def _load_config(self):
        self._bi_constants = {
            'ALL_HOSTS': 'ALL_HOSTS-f41e728b-0bce-40dc-82ea-51091d034fc3',
            'HOST_STATE': 'HOST_STATE-f41e728b-0bce-40dc-82ea-51091d034fc3',
            'HIDDEN': 'HIDDEN-f41e728b-0bce-40dc-82ea-51091d034fc3',
            'FOREACH_HOST':
            'FOREACH_HOST-f41e728b-0bce-40dc-82ea-51091d034fc3',
            'FOREACH_CHILD':
            'FOREACH_CHILD-f41e728b-0bce-40dc-82ea-51091d034fc3',
            'FOREACH_CHILD_WITH':
            'FOREACH_CHILD_WITH-f41e728b-0bce-40dc-82ea-51091d034fc3',
            'FOREACH_PARENT':
            'FOREACH_PARENT-f41e728b-0bce-40dc-82ea-51091d034fc3',
            'FOREACH_SERVICE':
            'FOREACH_SERVICE-f41e728b-0bce-40dc-82ea-51091d034fc3',
            'REMAINING': 'REMAINING-f41e728b-0bce-40dc-82ea-51091d034fc3',
            'DISABLED': 'DISABLED-f41e728b-0bce-40dc-82ea-51091d034fc3',
            'HARD_STATES': 'HARD_STATES-f41e728b-0bce-40dc-82ea-51091d034fc3',
            'DT_AGGR_WARN':
            'DT_AGGR_WARN-f41e728b-0bce-40dc-82ea-51091d034fc3',
        }
        self._hosttags_transformer = RulesetToDictTransformer(
            tag_to_group_map=get_tag_to_group_map(config.tags))

        try:
            vars_: Dict[str, Any] = {
                "aggregation_rules": {},
                "aggregations": [],
                "host_aggregations": [],
                "bi_packs": {},
            }
            vars_.update(self._bi_constants)

            exec(self._get_config_string(), vars_, vars_)

            # put legacy non-pack stuff into packs
            if (vars_["aggregation_rules"] or vars_["aggregations"] or vars_["host_aggregations"]) and \
                "default" not in vars_["bi_packs"]:
                vars_["bi_packs"]["default"] = {
                    "title": "Default Pack",
                    "rules": vars_["aggregation_rules"],
                    "aggregations": vars_["aggregations"],
                    "host_aggregations": vars_["host_aggregations"],
                    "public": True,
                    "contact_groups": [],
                }

            self._packs = {}
            for pack_id, pack in vars_["bi_packs"].items():
                # Convert rules from old-style tuples to new-style dicts
                aggregation_rules = {}
                for ruleid, rule in pack["rules"].items():
                    aggregation_rules[ruleid] = self._convert_rule_from_bi(
                        rule, ruleid)

                aggregations = []
                for aggregation in pack["aggregations"]:
                    aggregations.append(
                        self._convert_aggregation_from_bi(aggregation,
                                                          single_host=False))
                for aggregation in pack["host_aggregations"]:
                    aggregations.append(
                        self._convert_aggregation_from_bi(aggregation,
                                                          single_host=True))

                self._packs[pack_id] = {
                    "id": pack_id,
                    "title": pack["title"],
                    "rules": aggregation_rules,
                    "aggregations": aggregations,
                    "public": pack["public"],
                    "contact_groups": pack["contact_groups"],
                }

                self._add_missing_aggr_ids()
        except Exception as e:
            logger.error("Unable to load legacy bi.mk configuration %s",
                         str(e))
            raise
class BIManagement:
    def __init__(self):
        self._load_config()

    # .--------------------------------------------------------------------.
    # | Loading and saving                                                 |
    # '--------------------------------------------------------------------'

    def _get_config_string(self):
        filename = Path(cmk.utils.paths.default_config_dir, "multisite.d",
                        "wato", "bi.mk")
        with filename.open("rb") as f:
            return f.read()

    def _load_config(self):
        self._bi_constants = {
            'ALL_HOSTS': 'ALL_HOSTS-f41e728b-0bce-40dc-82ea-51091d034fc3',
            'HOST_STATE': 'HOST_STATE-f41e728b-0bce-40dc-82ea-51091d034fc3',
            'HIDDEN': 'HIDDEN-f41e728b-0bce-40dc-82ea-51091d034fc3',
            'FOREACH_HOST':
            'FOREACH_HOST-f41e728b-0bce-40dc-82ea-51091d034fc3',
            'FOREACH_CHILD':
            'FOREACH_CHILD-f41e728b-0bce-40dc-82ea-51091d034fc3',
            'FOREACH_CHILD_WITH':
            'FOREACH_CHILD_WITH-f41e728b-0bce-40dc-82ea-51091d034fc3',
            'FOREACH_PARENT':
            'FOREACH_PARENT-f41e728b-0bce-40dc-82ea-51091d034fc3',
            'FOREACH_SERVICE':
            'FOREACH_SERVICE-f41e728b-0bce-40dc-82ea-51091d034fc3',
            'REMAINING': 'REMAINING-f41e728b-0bce-40dc-82ea-51091d034fc3',
            'DISABLED': 'DISABLED-f41e728b-0bce-40dc-82ea-51091d034fc3',
            'HARD_STATES': 'HARD_STATES-f41e728b-0bce-40dc-82ea-51091d034fc3',
            'DT_AGGR_WARN':
            'DT_AGGR_WARN-f41e728b-0bce-40dc-82ea-51091d034fc3',
        }
        self._hosttags_transformer = RulesetToDictTransformer(
            tag_to_group_map=get_tag_to_group_map(config.tags))

        try:
            vars_: Dict[str, Any] = {
                "aggregation_rules": {},
                "aggregations": [],
                "host_aggregations": [],
                "bi_packs": {},
            }
            vars_.update(self._bi_constants)

            exec(self._get_config_string(), vars_, vars_)

            # put legacy non-pack stuff into packs
            if (vars_["aggregation_rules"] or vars_["aggregations"] or vars_["host_aggregations"]) and \
                "default" not in vars_["bi_packs"]:
                vars_["bi_packs"]["default"] = {
                    "title": "Default Pack",
                    "rules": vars_["aggregation_rules"],
                    "aggregations": vars_["aggregations"],
                    "host_aggregations": vars_["host_aggregations"],
                    "public": True,
                    "contact_groups": [],
                }

            self._packs = {}
            for pack_id, pack in vars_["bi_packs"].items():
                # Convert rules from old-style tuples to new-style dicts
                aggregation_rules = {}
                for ruleid, rule in pack["rules"].items():
                    aggregation_rules[ruleid] = self._convert_rule_from_bi(
                        rule, ruleid)

                aggregations = []
                for aggregation in pack["aggregations"]:
                    aggregations.append(
                        self._convert_aggregation_from_bi(aggregation,
                                                          single_host=False))
                for aggregation in pack["host_aggregations"]:
                    aggregations.append(
                        self._convert_aggregation_from_bi(aggregation,
                                                          single_host=True))

                self._packs[pack_id] = {
                    "id": pack_id,
                    "title": pack["title"],
                    "rules": aggregation_rules,
                    "aggregations": aggregations,
                    "public": pack["public"],
                    "contact_groups": pack["contact_groups"],
                }

                self._add_missing_aggr_ids()
        except Exception as e:
            logger.error("Unable to load legacy bi.mk configuration %s",
                         str(e))
            raise

    def _add_missing_aggr_ids(self):
        # Determine existing IDs
        used_aggr_ids = set()
        for pack_id, pack in self._packs.items():
            used_aggr_ids.update(
                {x["ID"]
                 for x in pack["aggregations"] if "ID" in x})

        # Compute missing IDs
        new_id = ""
        for pack_id, pack in self._packs.items():
            aggr_id_counter = 0
            for aggregation in pack["aggregations"]:
                if "ID" not in aggregation:
                    while True:
                        aggr_id_counter += 1
                        new_id = "%s_aggr_%d" % (pack_id, aggr_id_counter)
                        if new_id in used_aggr_ids:
                            continue
                        break
                    used_aggr_ids.add(new_id)
                    aggregation["ID"] = new_id

    def _convert_pack_to_bi(self, pack):
        converted_rules = {
            rule_id: self._convert_rule_to_bi(rule)
            for rule_id, rule in pack["rules"].items()
        }
        converted_aggregations: List[Tuple[BIAggrOptions, BIAggrGroups,
                                           BIAggrNode]] = []
        converted_host_aggregations: List[Tuple[BIAggrOptions, BIAggrGroups,
                                                BIAggrNode]] = []
        for aggregation in pack["aggregations"]:
            converted_aggregation = self._convert_aggregation_to_bi(
                aggregation)
            if aggregation["single_host"]:
                converted_host_aggregations.append(converted_aggregation)
            else:
                converted_aggregations.append(converted_aggregation)

        converted_pack = pack.copy()
        converted_pack["aggregations"] = converted_aggregations
        converted_pack["host_aggregations"] = converted_host_aggregations
        converted_pack["rules"] = converted_rules
        return converted_pack

    def _replace_bi_constants(self, s):
        for name, uuid in self._bi_constants.items():
            while True:
                n = s.replace("'%s'" % uuid, name)
                if n != s:
                    s = n
                else:
                    break
        return s[0] + '\n ' + s[1:-1] + '\n' + s[-1]

    def _convert_aggregation_to_bi(self, aggr):
        node = self._convert_node_to_bi(aggr["node"])
        option_keys: List[Tuple[str, Any]] = [
            ("ID", None),
            ("node_visualization", {}),
            ("hard_states", False),
            ("downtime_aggr_warn", False),
            ("disabled", False),
        ]

        if cmk_version.is_managed_edition():
            option_keys.append(("customer", managed.default_customer_id()))

        # Create dict with all aggregation options
        options = {}
        for option, default_value in option_keys:
            options[option] = aggr.get(option, default_value)

        return (options, self._convert_aggregation_groups(
            aggr["groups"])) + node

    def _convert_node_to_bi(self, node):
        if node[0] == "call":
            return node[1]
        if node[0] == "host":
            return (node[1][0], self._bi_constants['HOST_STATE'])
        if node[0] == "remaining":
            return (node[1][0], self._bi_constants['REMAINING'])
        if node[0] == "service":
            return node[1]
        if node[0] == "foreach_host":
            what = node[1][0]

            tags = node[1][1]
            if node[1][2]:
                hostspec = node[1][2]
            else:
                hostspec = self._bi_constants['ALL_HOSTS']

            if isinstance(what, tuple) and what[0] == 'child_with':
                child_conditions = what[1]
                what = what[0]
                child_tags = child_conditions[0]
                child_hostspec = child_conditions[1] if child_conditions[
                    1] else self._bi_constants['ALL_HOSTS']
                return (self._bi_constants["FOREACH_" + what.upper()], child_tags, child_hostspec, tags, hostspec) \
                       + self._convert_node_to_bi(node[1][3])
            return (self._bi_constants["FOREACH_" + what.upper()], tags,
                    hostspec) + self._convert_node_to_bi(node[1][3])
        if node[0] == "foreach_service":
            tags = node[1][0]
            if node[1][1]:
                spec = node[1][1]
            else:
                spec = self._bi_constants['ALL_HOSTS']
            service = node[1][2]
            return (self._bi_constants["FOREACH_SERVICE"], tags, spec,
                    service) + self._convert_node_to_bi(node[1][3])

    def _convert_aggregation_from_bi(self, aggr, single_host):
        if isinstance(aggr[0], dict):
            options = aggr[0]
            aggr = aggr[1:]
        else:
            # Legacy configuration
            options = {}
            if aggr[0] == self._bi_constants["DISABLED"]:
                options["disabled"] = True
                aggr = aggr[1:]
            else:
                options["disabled"] = False

            if aggr[0] == self._bi_constants["DT_AGGR_WARN"]:
                options["downtime_aggr_warn"] = True
                aggr = aggr[1:]
            else:
                options["downtime_aggr_warn"] = False

            if aggr[0] == self._bi_constants["HARD_STATES"]:
                options["hard_states"] = True
                aggr = aggr[1:]
            else:
                options["hard_states"] = False

        node = self._convert_node_from_bi(aggr[1:])
        aggr_dict = {
            "groups": self._convert_aggregation_groups(aggr[0]),
            "node": node,
            "single_host": single_host,
        }
        aggr_dict.update(options)
        return aggr_dict

    def _convert_aggregation_groups(self, old_groups):
        if isinstance(old_groups, list):
            return old_groups
        return [old_groups]

    # Make some conversions so that the format of the
    # valuespecs is matched
    def _convert_rule_from_bi(self, rule, ruleid):
        def tryint(x):
            try:
                return int(x)
            except ValueError:
                return x

        if isinstance(rule, tuple):
            rule = {
                "title": rule[0],
                "params": rule[1],
                "aggregation": rule[2],
                "nodes": rule[3],
            }
        crule = {}
        crule.update(rule)
        crule["nodes"] = list(map(self._convert_node_from_bi, rule["nodes"]))
        parts = rule["aggregation"].split("!")
        crule["aggregation"] = (parts[0], tuple(map(tryint, parts[1:])))
        crule["id"] = ruleid
        return crule

    def _convert_rule_to_bi(self, rule):
        brule = {}
        brule.update(rule)
        if "id" in brule:
            del brule["id"]
        brule["nodes"] = list(map(self._convert_node_to_bi, rule["nodes"]))
        brule["aggregation"] = "!".join([rule["aggregation"][0]] +
                                        list(map(str, rule["aggregation"][1])))
        return brule

    # Convert node-Tuple into format used by CascadingDropdown
    def _convert_node_from_bi(self, node):
        if len(node) == 2:
            if isinstance(node[1], list):
                return ("call", node)
            if node[1] == self._bi_constants['HOST_STATE']:
                return ("host", (node[0], ))
            if node[1] == self._bi_constants['REMAINING']:
                return ("remaining", (node[0], ))
            return ("service", node)

        foreach_spec = node[0]
        if foreach_spec == self._bi_constants['FOREACH_CHILD_WITH']:
            # extract the conditions meant for matching the childs
            child_conditions = list(node[1:3])
            if child_conditions[1] == self._bi_constants['ALL_HOSTS']:
                child_conditions[1] = None
            node = node[0:1] + node[3:]

            if not isinstance(child_conditions[0], dict):
                new_tags = self._hosttags_transformer.transform_host_tags(
                    child_conditions[0])
                child_conditions[0] = new_tags.get("host_tags", {})

        # Extract the list of tags
        if isinstance(node[1], (list, dict)):
            tags = node[1]
            node = node[0:1] + node[2:]
            if not isinstance(tags, dict):
                new_tags = self._hosttags_transformer.transform_host_tags(tags)
                tags = new_tags.get("host_tags", {})
        else:
            tags = {}

        hostspec = node[1]
        if hostspec == self._bi_constants['ALL_HOSTS']:
            hostspec = None

        if foreach_spec == self._bi_constants['FOREACH_SERVICE']:
            service = node[2]
            subnode = self._convert_node_from_bi(node[3:])
            return ("foreach_service", (tags, hostspec, service, subnode))

        subnode = self._convert_node_from_bi(node[2:])
        if foreach_spec == self._bi_constants['FOREACH_HOST']:
            what: Union[str, Tuple] = "host"
        elif foreach_spec == self._bi_constants['FOREACH_CHILD']:
            what = "child"
        elif foreach_spec == self._bi_constants['FOREACH_CHILD_WITH']:
            what = ("child_with", child_conditions)
        elif foreach_spec == self._bi_constants['FOREACH_PARENT']:
            what = "parent"
        return ("foreach_host", (what, tags, hostspec, subnode))