"""
Configuration loader module
"""

import os
import shutil
from pathlib import Path
from typing import Optional, Type, TypeVar, TypedDict, cast
import yaml

import zscams
from zscams.agent.src.support.dicts import merge
from zscams.agent.src.support.yaml import YamlIndentedListsDumper, resolve_placeholders

ROOT_PATH = Path(zscams.__file__).resolve().parent.joinpath("agent")
CONFIG_PATH = os.path.join(ROOT_PATH.absolute(), "config.yaml")

GetReturnT = TypeVar("GetReturnT")


class MissingConfiguration(BaseException):
    """Error when the requisted key is not found"""


class Configuration:
    """Class to get/set configurations and handle the configurations file."""

    __instance = None
    __config = {}

    def __init__(self):
        self.__load_config()

    def __new__(cls):
        if cls.__instance is None:
            cls.__instance = super().__new__(cls)
        return cls.__instance

    def __load_config(self):
        try:
            with open(CONFIG_PATH, "r", encoding="utf-8") as f:
                self.__config = yaml.safe_load(f)

            user_config = self.__get_user_config()
            if user_config is not None:
                merge(user_config, self.__config)

        except FileNotFoundError:
            shutil.copyfile(ROOT_PATH.joinpath("configuration/config.j2"), CONFIG_PATH)
            self.__load_config()

    def __get_user_config(self):
        if not self.__config or self.__config.get("config_dir") is None:
            return

        user_config_dir = Path(self.__config.get("config_dir", ""))
        if not user_config_dir.exists():
            return

        user_config = user_config_dir.joinpath("config.yaml")
        if not user_config.exists():
            alt_user_config = user_config_dir.joinpath("config.yml")
            if alt_user_config.exists():
                user_config = alt_user_config
            else:
                return

        file_content = user_config.read_text(encoding="utf-8")
        if not file_content.strip():
            return

        return yaml.safe_load(file_content)

    def override_config(self, new_config: dict):
        """
        Override the existing configuration with a new one.
        Args:
            new_config (dict): New configuration dictionary to override the existing one.
        """
        self.__config = new_config

        with open(CONFIG_PATH, "w", encoding="utf-8") as file:
            yaml.dump(
                self.__config,
                file,
                Dumper=YamlIndentedListsDumper,
                default_flow_style=False,
                explicit_start=True,
            )

    def to_dict(self):
        return self.__config

    def get(
        self,
        key: str,
        default: Optional[GetReturnT] = None,
        must_resolve=False,
        valtyp: Type[GetReturnT] = str,
    ) -> GetReturnT:
        """
        Returns the configuration value.

        Args:
            key (str): The configurations dotnotation key to get the value of.
            default (Optional[T]): The default value if the configurations wasn't found.
            must_resolve (bool): Wheither to throw an error if the value wasn't found and if there was no default.
            valtyp (Type[T]): The expected type of the return value.
        """

        keys = key.split(".")
        val = self.__config

        for k in keys:
            if isinstance(val, dict) and k in val:
                val = val.get(k, default)
            elif isinstance(val, list) and int(k) < len(val):
                val = val[int(k)]
            elif default is not None:
                val = default
                break
            elif must_resolve:
                raise MissingConfiguration
            else:
                val = None
                break

        return cast(GetReturnT, val)

    def must_get(
        self,
        key: str,
        default: Optional[GetReturnT] = None,
        valtyp: Type[GetReturnT] = str,
    ) -> GetReturnT:
        return self.get(key, default=default, must_resolve=True, valtyp=valtyp)

    def has(self, key: str):
        try:
            self.get(key, must_resolve=True)
            return True
        except MissingConfiguration:
            return False

    def reinitialize(self, **kwargs):
        """Regenerate the config template with new values"""
        template_path = ROOT_PATH.joinpath("configuration/config.j2")
        with open(template_path, encoding="utf-8") as f:
            template = yaml.safe_load(f)
        resolve_placeholders(template, kwargs)
        self.override_config(template)


class RemoteConfig(TypedDict):
    """Type definition for remote configuration."""

    host: str
    port: str
    verify_cert: bool
    client_key: str
    client_cert: str
    ca_cert: str
    ca_chain: str


config = Configuration()
