import re
from collections import defaultdict
from fnmatch import fnmatch
from os.path import dirname
from pathlib import Path, PurePath
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union

from pyterraformer.constants import logger
from pyterraformer.core.generics import BlockList, Literal
from pyterraformer.core.utility import get_root
from pyterraformer.serializer import BaseSerializer
from pyterraformer.terraform import Terraform

if TYPE_CHECKING:
    from pyterraformer.core.generics.variables import Variable
    from pyterraformer.core.namespace import TerraformFile


def process_attribute(input: Any, level=0):
    from pyterraformer.core.generics import Variable

    # if isinstance(input, Block):
    #     input = {input.name: input._keys}

    if not isinstance(input, dict):
        return input
    output: dict[str, Any] = {}
    for key, item in input.items():
        if isinstance(item, Variable):
            output[key] = item.render_basic()
        # elif isinstance(item, StringLit):
        #     output[key] = item.__repr__()
        elif isinstance(item, Literal):
            output[key] = item
        elif isinstance(item, BlockList):
            for idx, sub_item in enumerate(item):
                output[f"{key}~~block_{idx}"] = process_attribute(sub_item)
        elif isinstance(item, dict):
            output[key] = process_attribute(item)
        elif isinstance(item, list):
            output[key] = [process_attribute(sub_item) for sub_item in item]
        else:
            output[key] = item
    return output


class LazyFileDict(dict):
    def __init__(self, *args):
        dict.__init__(self, args)

    def iter(self):
        from pyterraformer.core.namespace import LazyFile

        for key, value in super().iter():
            if isinstance(value, LazyFile):
                value = value.resolve()
                self[key] = value
            yield key, value

    def items(self, resolve=True):
        from pyterraformer.core.namespace import LazyFile

        for key, value in super().items():
            if isinstance(value, LazyFile) and resolve:
                value = value.resolve()
                self[key] = value
            yield key, value

    def __getitem__(self, key):
        from pyterraformer.core.namespace import LazyFile

        val = dict.__getitem__(self, key)
        if isinstance(val, LazyFile):
            val = val.resolve()
            self[key] = val
        return val

    def __setitem__(self, key, val):
        dict.__setitem__(self, key, val)


def extract_errors(input: str):
    found = re.findall(r"(Error:.*)(?:Error:|$)", input, re.IGNORECASE | re.MULTILINE | re.DOTALL)
    return "\n".join(found).strip()


class TerraformWorkspace:
    def __init__(
        self,
        path: str | PurePath,
        terraform: Terraform | None = None,
        serializer: BaseSerializer | None = None,
        files: list["TerraformFile"] | None = None,
        children: list["TerraformWorkspace"] | None = None,
    ):
        self.terraform = terraform
        self.path = str(path)
        self._path = Path(self.path)
        self.files: dict[str, TerraformFile] = LazyFileDict()
        if files:
            for file in files:
                self.files[file.name] = files
        self.children: list[TerraformWorkspace] = children or []
        self.name = self._path.stem
        self.variables: dict[str, Variable] = {}
        self.data: list = []
        self.serializer = serializer

    def apply(self):
        return self.terraform.run(["apply", "--auto-approve"], path=self._path)

    @property
    def terraform_path(self):
        return get_root(self._path)

    def relative_path(self, path: str):
        return get_root(str(self._path), path)

    def get_file_safe(self, name: str) -> "TerraformFile":
        from pyterraformer.core.namespace import TerraformFile

        out = self.files.get(name)
        if not out:
            try:
                if not self.serializer:
                    raise FileNotFoundError("No valid serializer found.")
                file = self.serializer.parse_file(self._path / name, self)
            except FileNotFoundError:
                file = TerraformFile(workspace=self, text="", location=self._path / name)

            self.files[name] = file

        return self.files[name]

    def get_terraform_config(self):
        from pyterraformer.core.generics import BlockList, TerraformConfig

        terraform = self.get_file_safe("terraform.tf")
        existing = [obj for obj in terraform.objects if isinstance(obj, TerraformConfig)]
        if existing:
            return existing[0]
        logger.info("creating new terraform config")
        config = TerraformConfig(
            backend=self.terraform.backend.as_object(),
            required_providers=BlockList([{}]),
        )
        terraform.add_object(config)
        return config

    def add_provider(self, name: str, source: str, **kwargs):
        existing = self.get_terraform_config()
        required_providers = getattr(existing, "required_providers", BlockList([{}]))
        required_providers[0][name] = {"source": source, **kwargs}
        existing.required_providers = required_providers

    def add_file(self, file: Union["TerraformFile", PurePath, str]) -> "TerraformFile":
        from pyterraformer.core.namespace import TerraformFile

        if isinstance(file, TerraformFile):
            self.files[file.name] = file
            return file
        elif str(file) in self.files:
            return self.files[str(file)]
        else:
            nfile = TerraformFile(workspace=self, text="", location=self._path / file)
            self.files[nfile.name] = nfile
            return nfile

    def add_child_workspace(self, path: str):
        child = TerraformWorkspace(path=path, serializer=self.serializer, terraform=self.terraform)
        self.children.append(child)
        setattr(self, child.name, child)

    def add_variable(self, key: str, values, exists_okay: bool = False, replace: bool = False):
        from pyterraformer.core.generics.variables import Variable

        if key in self.variables:
            if exists_okay:
                return self.variables[key]
            elif replace:
                pass
            else:
                raise ValueError(f"Key {key} already exists in variables!")

        if isinstance(values, dict):
            vtype = "map"
            variable = Variable(None, key, [["type", vtype], ["default", values]])
        elif isinstance(values, str):
            vtype = "string"
            variable = Variable(None, key, [["type", vtype], ["default", values]])
        elif isinstance(values, Variable):
            variable = values
        else:
            raise ValueError(f"Unable to map {type(values)} to a variable type")

        self.variables[key] = variable
        # create if not exists
        if "variables.tf" not in self.files:
            self.files["variables.tf"] = TerraformFile(self, "", self._path / "variables.tf")

        self.files["variables.tf"].add_object(variable, replace=replace)
        return variable

    def delete_variable(self, key: str, safe=False):
        try:
            variable = self.variables[key]
        except KeyError as e:
            if safe:
                return
            raise e
        self.files["variables.tf"].delete_object(variable)

    def save(self, format: bool = True, apply: bool = False):
        from pyterraformer.core.namespace import LazyFile

        if not self.serializer:
            raise ValueError("Cannot save without serializer defined.")
        format = False
        for _key, file in self.files.items(resolve=False):  # type: ignore
            # skip files we never touched
            if isinstance(file, LazyFile):
                logger.info("Skipping lazily unparsed file")
                continue
            file.save(self.serializer)
            if str(file.location).endswith(".tf"):
                format = True
        if format and self.terraform:
            try:
                self.terraform.run(["fmt"], path=dirname(self.path))
            except Exception as e:
                raise e
        if apply:
            self.apply()

    def save_all(self, format=True):
        self.save(format)
        for ws in self.children:
            ws.save_all(format)

    def find(self, object_type, invert=False):
        from pyterraformer.core.namespace import LazyFile

        output = defaultdict(list)
        replace = {}
        # resolve any files, so we can search them
        for key, file in self.files.items():
            if isinstance(file, LazyFile):
                replace[key] = file.resolve()
        for key, item in replace.items():
            self.files[key] = item
        for key, file in self.files.items():
            for object in file.objects:
                if isinstance(object, object_type):
                    output[key].append(object)
        if invert:
            out_list = {}
            for key, items in output.items():
                for item in items:
                    out_list[item] = key
            return out_list
        return output

    def get_object(self, **kwargs):
        for _key, file in self.files.items():
            try:
                return file.get_object(**kwargs)
            except ValueError:
                pass
        raise ValueError(f"No object matching filter criteria {kwargs} found in any files in {self.path}")


def value_match(item: Any, value: Any) -> bool:
    if isinstance(value, str):
        # first check for string comparison
        if item == value:
            return True
        # then fnmatch with quotes replaced
        return fnmatch(str(item).replace('"', ""), value)
    else:
        return item == value
