"""
The sls resolver is used to gather sls files, render them and return the initial
phase 1 highdata. This involves translating sls references into file paths,
downloading those sls files and then rendering them.

Once an sls file is rendered the include statements are resolved as well.
"""
import re


async def gather(hub, name, *sls):
    """
    Gather the named sls references
    """
    await hub.idem.resolve.get_blocks(name, sls)
    await hub.idem.resolve.render(name)


async def get_blocks(hub, name, sls):
    for sls_ref in sls:
        cfn = await hub.idem.get.ref(name, sls_ref)
        if not cfn:
            hub.idem.RUNS[name]["errors"].append(
                f"SLS ref {sls_ref} did not resolve to a file"
            )
            continue
        blocks = hub.rend.init.blocks(cfn)
        hub.idem.RUNS[name]["blocks"][sls_ref] = blocks
        hub.idem.RUNS[name]["sls_refs"][sls_ref] = cfn
        hub.idem.RUNS[name]["resolved"].add(sls_ref)
        hub.idem.RUNS[name]["files"].add(cfn)


async def render(hub, name):
    """
    Pop the available blocks and render them if they have satisfied requisites
    """
    rendered = {}
    for sls_ref, blocks in hub.idem.RUNS[name]["blocks"].items():
        cfn = hub.idem.RUNS[name]["sls_refs"][sls_ref]
        for bname, block in blocks.items():
            clear = True
            for key, val in block.get("keys", {}).items():
                # TODO: This should be an additional render requisite plugin
                # subsystem, change it to a subsystem as soon as any new conditionals
                # are added!!
                clear = False
                if key == "require":
                    for tag, data in hub.idem.RUNS[name]["running"].items():
                        if data["name"] == val:
                            clear = True
                            break
                if clear:
                    continue
            if clear:
                state = await hub.rend.init.parse_bytes(
                    block, hub.idem.RUNS[name]["render"]
                )
                await hub.idem.resolve.introduce(name, state, sls_ref, cfn)
                rendered[sls_ref] = bname
    for sls_ref, bname in rendered.items():
        hub.idem.RUNS[name]["blocks"][sls_ref].pop(bname)


async def introduce(hub, name, state, sls_ref, cfn):
    """
    Introduce the raw state into the running dataset
    """
    if not isinstance(state, dict):
        hub.idem.RUNS[name]["errors"].append(
            f"SLS {sls_ref} is not formed as a dict but as a {type(state)}"
        )
        return
    if "include" in state:
        if not isinstance(state["include"], list):
            hub.idem.RUNS[name]["errors"].append(
                f'Include Declaration in SLS {sls_ref} is not formed as a list but as a {type(state["include"])}'
            )
        include = state.pop("include")
    else:
        include = []
    hub.idem.resolve.extend(name, state, sls_ref)
    hub.idem.resolve.exclude(name, state, sls_ref)
    hub.idem.resolve.decls(name, state, sls_ref)
    hub.idem.resolve.iorder(name, state)
    await hub.idem.resolve.includes(name, include, state, sls_ref, cfn)
    hub.idem.RUNS[name]["high"].update(state)


def iorder(hub, name, state):
    """
    Take a state and apply the iorder system
    """
    for id_ in state:
        for s_dec in state[id_]:
            if not isinstance(s_dec, str):
                # PyDSL OrderedDict?
                continue

            if not isinstance(state[id_], dict):
                # Include's or excludes as lists?
                continue
            if not isinstance(state[id_][s_dec], list):
                # Bad syntax, let the verify seq pick it up later on
                continue

            found = False
            if s_dec.startswith("_"):
                continue

            for arg in state[id_][s_dec]:
                if isinstance(arg, dict):
                    if len(arg) > 0:
                        if next(iter(arg)) == "order":
                            found = True
            if not found:
                if not isinstance(state[id_][s_dec], list):
                    # quite certainly a syntax error, managed elsewhere
                    continue
                state[id_][s_dec].append({"order": hub.idem.RUNS[name]["iorder"]})
                hub.idem.RUNS[name]["iorder"] += 1


def extend(hub, name, state, sls_ref):
    """
    Resolve the extend statement
    """
    if "extend" in state:
        ext = state.pop("extend")
        if not isinstance(ext, dict):
            hub.idem.RUNS[name]["errors"].append(
                f'Extension value in SLS "{sls_ref}" is not a dictionary'
            )
            return
        for id_ in ext:
            if not isinstance(ext[id_], dict):
                hub.idem.RUNS[name]["errors"].append(
                    f'Extension ID "{id_}" in SLS "{sls_ref}" is not a dictionary'
                )
                continue
            if "__sls__" not in ext[id_]:
                ext[id_]["__sls__"] = sls_ref
            # if '__env__' not in ext[id_]:
            #    ext[id_]['__env__'] = saltenv
            for key in list(ext[id_]):
                if key.startswith("_"):
                    continue
                if not isinstance(ext[id_][key], list):
                    continue
                if "." in key:
                    comps = key.split(".")
                    ext[id_][comps[0]] = ext[id_].pop(key)
                    ext[id_][comps[0]].append(comps[1])
        state.setdefault("__extend__", []).append(ext)


def exclude(hub, name, state, sls_ref):
    """
    Resolve any exclude statements
    """
    if "exclude" in state:
        exc = state.pop("exclude")
        if not isinstance(exc, list):
            hub.idem.RUNS[name]["errors"].append(
                f"Exclude Declaration in SLS {sls_ref} is not formed as a list"
            )
        state.setdefault("__exclude__", []).extend(exc)


def decls(hub, name, state, sls_ref):
    """
    Resolve and state formatting and data insertion
    """
    for id_ in state:
        if not isinstance(state[id_], dict):
            if id_ == "__extend__":
                continue
            if id_ == "__exclude__":
                continue

            if isinstance(state[id_], str):
                # Is this is a short state, it needs to be padded
                if "." in state[id_]:
                    comps = state[id_].split(".")
                    state_ref = ".".join(comps[:-1])
                    state[id_] = {"__sls__": sls_ref, state_ref: [comps[-1]]}
                    continue
            hub.idem.RUNS[name]["errors"].append(
                f"ID {id_} in SLS {sls_ref} is not a dictionary"
            )
            continue
        skeys = set()
        for key in list(state[id_]):
            if key.startswith("_"):
                continue
            if not isinstance(state[id_][key], list):
                continue
            if "." in key:
                comps = key.split(".")
                # Idem doesn't support state files such as:
                #
                #     /etc/redis/redis.conf:
                #       file.managed:
                #         - user: redis
                #         - group: redis
                #         - mode: 644
                #       file.comment:
                #           - regex: ^requirepass
                ref = ".".join(comps[:-1])
                if ref in skeys:
                    hub.idem.RUNS[name]["errors"].append(
                        f'ID "{id_}" in SLS "{sls_ref}" contains multiple state declarations of the same type'
                    )
                    continue
                state[id_][ref] = state[id_].pop(key)
                state[id_][ref].append(comps[-1])
                skeys.add(ref)
                continue
            skeys.add(key)
        if "__sls__" not in state[id_]:
            state[id_]["__sls__"] = sls_ref


async def includes(hub, name, include, state, sls_ref, cfn):
    """
    Parse through the includes and download not-yet-resolved includes
    """
    for inc_sls in include:
        if inc_sls.startswith("."):
            match = re.match(r"^(\.+)(.*)$", inc_sls)
            if match:
                levels, include = match.groups()
            else:
                hub.idem.RUNS[name]["errors"].append(
                    f'Badly formatted include {inc_sls} found in SLS "{sls_ref}"'
                )
                continue
            level_count = len(levels)
            p_comps = sls_ref.split(".")
            if cfn.endswith("/init.sls"):
                p_comps.append("init")
            if level_count > len(p_comps):
                hub.idem.RUNS[name]["errors"].append(
                    f'Attempted relative include of "{inc_sls}" within SLS {sls_ref} goes beyond top level package'
                )
                continue
            inc_sls = ".".join(p_comps[:-level_count] + [include])
        if inc_sls not in hub.idem.RUNS[name]["resolved"]:
            await hub.idem.sls.resolve.gather(name, inc_sls)
