import importlib
import os
from dataclasses import dataclass, field

from loguru import logger

from stela.config import StelaOptions
from stela.exceptions import StelaEnvironmentNotFoundError
from stela.parsers.dotenv import read_dotenv
from stela.utils import show_value


@dataclass
class StelaMain:
    options: StelaOptions
    settings: dict[str, any] = field(default_factory=dict)

    def __post_init__(self) -> None:
        """Save original environment keys.

        This is needed to avoid overwriting true system environment variables
        but to override values that were previously created by dotenv files (like .env)
        when reading the same variable again, in new dotenv files
        (like .env.local or .env.production).
        """
        original_keys = os.environ.get("_ORIGINAL_ENVIRON_KEYS", "")
        if not original_keys:
            os.environ["_ORIGINAL_ENVIRON_KEYS"] = ",".join(sorted(os.environ.keys()))

    def log_current_data(self, origin: str) -> None:
        def log_dict(dict_key: str, dict_value: any, parents: list):
            if isinstance(dict_value, dict):
                for k, v in dict_value.items():
                    if dict_key not in parents:
                        parents.append(dict_key)
                    log_dict(k, v, parents)
            else:
                parents_key = (
                    parents + [dict_key] if dict_key not in parents else parents
                )
                logger.debug(
                    f"[{origin}] {'.'.join(parents_key)} = "
                    f"{show_value(dict_value, self.options.log_filtered_value)}"
                )

        for key, value in self.settings.items():
            log_dict(key, value, [])

    @property
    def environment(self) -> str:
        """Return Current Environment."""
        if not self.options.current_environment:
            raise StelaEnvironmentNotFoundError("Environment not found.")
        return self.options.current_environment

    def get_project_settings(self) -> None:
        env_data = self.read_env_files()

        module_path = ".".join(self.options.final_loader.split(".")[:-1])
        loader_fn = self.options.final_loader.split(".")[-1]
        module = importlib.import_module(module_path)
        self.settings = getattr(module, loader_fn)(
            options=self.options, env_data=env_data
        )
        # Preserve existing system env values; allow loader to override only dotenv-injected keys
        forbidden_keys = os.getenv("_ORIGINAL_ENVIRON_KEYS", "").split(",")
        for k, v in self.settings.items():
            if v is None:
                continue
            if k not in forbidden_keys:
                os.environ[k] = v
        if self.options.show_logs:
            logger.debug(
                f"Settings after running loader: "
                f"{[f'{k}={show_value(v, self.options.log_filtered_value)}' for k, v in self.settings.items()]}"
            )

    def read_env_files(self) -> dict[str, any]:
        settings = {}
        current_environment = self.options.current_environment
        dotenv_files = [
            self.options.env_file,
            f"{self.options.env_file}.local",
        ]
        if current_environment:
            dotenv_files += [
                f"{self.options.env_file}.{current_environment.lower()}",
                f"{self.options.env_file}.{current_environment.lower()}.local",
            ]
        if self.options.show_logs:
            logger.debug(f"Looking for dotenv files: {dotenv_files}")
        for file in dotenv_files:
            env_settings = read_dotenv(
                config_file_path=self.options.config_file_path,
                env_file=file,
                encoding=self.options.dotenv_encoding,
                verbose=self.options.warn_if_env_is_missing,
                show_logs=self.options.show_logs,
                filter_logs=self.options.log_filtered_value,
            )
            settings |= env_settings
        # Preserve existing system env values; allow loader to override only dotenv-injected keys
        forbidden_keys = os.getenv("_ORIGINAL_ENVIRON_KEYS", "").split(",")
        for k, v in settings.items():
            if v is None:
                continue
            if k not in forbidden_keys:
                os.environ[k] = v
        if self.options.show_logs:
            logger.debug(
                f"Settings after load all dotenv files: "
                f"{[f'{k}={show_value(v, self.options.log_filtered_value)}' for k, v in settings.items()]}"
            )
        return settings


def default_loader(options: StelaOptions, env_data: dict[str, any]) -> dict[str, any]:
    """Stela Default Loader.

    :param options: Stela Options
    :param env_data: Dict with environment data
    :return: Dict
    """
    from loguru import logger

    logger.info(
        f"Using Stela Default Loader. Current environment is: {options.current_environment}"
    )
    return env_data
