"""
Utilitários para carregar e expandir variáveis de ambiente, incluindo suporte para arquivos de configuração de projeto e
tipos de projeto.
"""

from __future__ import annotations

import os
from pathlib import Path
from typing import Any

from common import print_message
from src.datalake.commons.ci_utils import GREEN, RED, RESET, load_env_from_files

SCRIPT_DIR = Path(__file__).resolve().parent

DATA_CONFIG_DEFAULT_FILE = ".data_config"

DATA_CONFIG_LOCAL_FILE = ".config"
DATA_CONFIG_DP_FILE = ".dpconfig"


def _bool_non_empty(value: str | None) -> bool:
    """
    Converte uma string em um valor booleano, considerando strings vazias como False.

    Args:
        value (str | None): A string a ser avaliada.

    Returns:
        bool: True se a string não for vazia, False caso contrário.

    """
    return bool(value and value.strip())


def load_project_environment(
    repo_root: Path, base_env: dict[str, str] | None = None
) -> dict[str, str]:
    """
    Carrega o ambiente do projeto a partir dos arquivos de configuração.

    Args:
        repo_root (Path): Caminho para o diretório raiz do repositório.
        base_env (dict[str, str] | None): Ambiente base para carregar as variáveis.

    Returns:
        dict[str, str]: Ambiente do projeto carregado.

    """
    files_to_load = []

    config_loaded = False
    config_file = repo_root / DATA_CONFIG_LOCAL_FILE
    if config_file.exists():
        print_message(f"{GREEN}[Dados] .config encontrado, será importado")
        files_to_load.append(config_file)
        config_loaded = True
    else:
        print_message("[Dados] Warning: .config não encontrado", debug_message=True)

    dpconfig_file = repo_root / DATA_CONFIG_DP_FILE
    if dpconfig_file.exists():
        print_message(
            f"{GREEN}[Dados] {DATA_CONFIG_DP_FILE} encontrado, será importado"
        )
        files_to_load.append(dpconfig_file)
        config_loaded = True
    else:
        print_message(
            f"[Dados] Warning: {DATA_CONFIG_DP_FILE} não encontrado", debug_message=True
        )

    if not config_loaded:
        raise RuntimeError(
            f"{RED}[Dados] Erro: Nenhum arquivo de configuração de projeto foi carregado "
            f"({DATA_CONFIG_LOCAL_FILE} ou {DATA_CONFIG_DP_FILE} necessário){RESET}"
        )

    return load_env_from_files(files_to_load, base_env=base_env)


def load_data_ci_environment(
    base_env: dict[str, str] | None = None, ignore_project_type: bool = False
) -> dict[str, str]:
    """
    Carrega o ambiente de CI de dados, incluindo configurações padrão, do projeto e específicas do tipo de projeto.

    Args:
        base_env (dict[str, str] | None): Ambiente base para carregar as variáveis.
        ignore_project_type (bool): Se True, ignora as configurações específicas do tipo de projeto.

    Returns:
        dict[str, str]: Ambiente de CI de dados carregado.

    """
    env = dict(base_env or os.environ)
    repo_root = Path.cwd()

    print_message(
        f"{GREEN}[Dados] Importando configurações padrões de configuração."
        f" Ignorar configuração de projeto: {ignore_project_type}"
    )

    files_to_load = [
        SCRIPT_DIR / DATA_CONFIG_DEFAULT_FILE,
        SCRIPT_DIR / ".." / DATA_CONFIG_DEFAULT_FILE,
        SCRIPT_DIR / ".." / ".." / DATA_CONFIG_DEFAULT_FILE,
    ]

    loaded_env = load_env_from_files(files_to_load, base_env=env, system_vars=True)

    # Carrega uma vez pra verificar se tem `DATAOFFICE_PROJECT_TYPE`
    loaded_env = load_project_environment(repo_root, base_env=loaded_env)

    files_to_load = []
    project_type = loaded_env.get("DATAOFFICE_PROJECT_TYPE", "").strip()
    project_type_file_name = f".{project_type}_config"
    if not ignore_project_type and project_type:
        files_to_load.append(SCRIPT_DIR / project_type_file_name)
        files_to_load.append(SCRIPT_DIR / ".." / project_type_file_name)
        files_to_load.append(SCRIPT_DIR / ".." / ".." / project_type_file_name)
        loaded_env = load_env_from_files(
            files_to_load, base_env=loaded_env, system_vars=True
        )
        print_message(
            f"[Dados] Importando configurações padrões de configuração do tipo: "
            f"DATAOFFICE_PROJECT_TYPE=`{project_type}`"
        )

        # Carrega novamente para pegar as variáveis de ambiente do projeto
        loaded_env = load_project_environment(repo_root, base_env=loaded_env)

        files_to_load = []
        if not ignore_project_type and project_type:
            project_step_file = repo_root / f".{project_type}_config"
            if project_step_file.exists():
                files_to_load.append(project_step_file)
                print_message(
                    f"[Dados] Importando configurações de projeto do tipo: DATAOFFICE_PROJECT_TYPE=`{project_type}`"
                )

        loaded_env = load_env_from_files(files_to_load, base_env=loaded_env)

    if ignore_project_type:
        loaded_env.pop("DATAOFFICE_PROJECT_TYPE", None)

    print_message(
        f"[Dados] Configurações de ambiente importadas - SOURCE_SYSTEM: {loaded_env.get('SOURCE_SYSTEM', '')} {RESET}"
    )
    return loaded_env


all_env_setted = False


def resolve_by_env(expanded: dict, suffix: str) -> str:
    """
    Resolve uma variável de ambiente específica para o ambiente atual.

    Args:
        expanded (dict): Dicionário contendo as variáveis de ambiente expandidas.
        suffix (str): Sufixo da variável de ambiente a ser resolvida.

    Returns:
        str: Valor da variável de ambiente específica para o ambiente atual.

    """
    environment = expanded.get("ENVIRONMENT", "")
    return expanded.get(f"{environment.upper()}_{suffix}", "")


def set_all_env(env: dict[str, str], force: bool = False) -> dict[str, str]:
    """
    Define todas as variáveis de ambiente necessárias para o projeto.

    Args:
        env (dict[str, str]): Dicionário contendo as variáveis de ambiente atuais.

    Returns:
        dict[str, str]: Dicionário contendo todas as variáveis de ambiente definidas.

    """
    global all_env_setted
    if all_env_setted and not force:
        print_message(
            f"{GREEN}[Dados] Variáveis de ambiente já definidas, pulando expansão...{RESET}"
        )
        return env

    expanded = dict(env)
    environment = expanded.get("ENVIRONMENT", "")
    print_message(f"[Dados] env - definindo variáveis - {environment}")

    base_stack_name = expanded.get("STACK_NAME", "")
    if base_stack_name.startswith(f"{environment}-"):
        expanded["BASE_STACK_NAME"] = base_stack_name[len(f"{environment}-") :]

    stack_name = expanded.get("STACK_NAME", "")
    if not stack_name.startswith(f"{environment}-"):
        expanded["STACK_NAME"] = f"{environment}-{expanded.get('STACK_NAME', '')}"

    anonymous_agreement_term_table_name = expanded.get(
        "ANONYMOUS_AGREEMENT_TERM_TABLE_NAME", ""
    )
    if not anonymous_agreement_term_table_name.startswith(f"{environment}-"):
        expanded["ANONYMOUS_AGREEMENT_TERM_TABLE_NAME"] = (
            f"{environment}-{expanded.get('ANONYMOUS_AGREEMENT_TERM_TABLE_NAME', '')}"
        )

    tenant_agreement_term_table_name = expanded.get(
        "TENANT_AGREEMENT_TERM_TABLE_NAME", ""
    )
    if not tenant_agreement_term_table_name.startswith(f"{environment}-"):
        expanded["TENANT_AGREEMENT_TERM_TABLE_NAME"] = (
            f"{environment}-{expanded.get('TENANT_AGREEMENT_TERM_TABLE_NAME', '')}"
        )

    fix_bucket_names(expanded, environment)

    expanded["DEPENDENCIES_LAYER_VERSION"] = resolve_by_env(
        expanded, "DEPENDENCIES_LAYER_VERSION"
    )
    expanded["DEPENDENCIES_ML_LAYER_VERSION"] = resolve_by_env(
        expanded, "DEPENDENCIES_ML_LAYER_VERSION"
    )
    expanded["DATALAKE_COMMONS_LAYER_VERSION"] = resolve_by_env(
        expanded, "DATALAKE_COMMONS_LAYER_VERSION"
    )
    expanded["SCHEDULE_ENABLED"] = resolve_by_env(expanded, "SCHEDULE_ENABLED")
    expanded["SCHEDULE_EXPRESSION"] = resolve_by_env(expanded, "SCHEDULE_EXPRESSION")
    expanded["NUMBER_OF_WORKERS_LOW"] = resolve_by_env(
        expanded, "NUMBER_OF_WORKERS_LOW"
    )
    expanded["WORKER_TYPE_LOW"] = resolve_by_env(expanded, "WORKER_TYPE_LOW")
    expanded["NUMBER_OF_WORKERS"] = resolve_by_env(expanded, "NUMBER_OF_WORKERS")
    expanded["WORKER_TYPE"] = resolve_by_env(expanded, "WORKER_TYPE")
    expanded["NUMBER_OF_WORKERS_POWER"] = resolve_by_env(
        expanded, "NUMBER_OF_WORKERS_POWER"
    )
    expanded["WORKER_TYPE_POWER"] = resolve_by_env(expanded, "WORKER_TYPE_POWER")
    expanded["LAMBDA_CONCURRENT_EXECUTIONS"] = resolve_by_env(
        expanded, "LAMBDA_CONCURRENT_EXECUTIONS"
    )
    expanded["ZIP_LAMBDA_CONCURRENT_EXECUTIONS"] = resolve_by_env(
        expanded, "ZIP_LAMBDA_CONCURRENT_EXECUTIONS"
    )
    expanded["FILE_LAMBDA_CONCURRENT_EXECUTIONS"] = resolve_by_env(
        expanded, "FILE_LAMBDA_CONCURRENT_EXECUTIONS"
    )
    expanded["TRANSIENT_PROCESSOR_FN_VERSION"] = resolve_by_env(
        expanded, "TRANSIENT_PROCESSOR_FN_VERSION"
    )
    expanded["LOG_ENABLED"] = "true"

    if environment == "dev":
        expanded["INTERVAL_IN_SECONDS_FOR_STREAMING"] = "300"
        expanded["LOG_LEVEL"] = "INFO"
        expanded["LOG_RETENTION_IN_DAYS"] = "7"
        expanded["LIFECYCLE_CONFIGURATION_STATUS"] = "Disabled"
        expanded["LIST_IGNORE_TENANTS"] = "401"
    else:
        expanded["INTERVAL_IN_SECONDS_FOR_STREAMING"] = "900"
        expanded["LOG_LEVEL"] = "WARNING"
        expanded["LOG_RETENTION_IN_DAYS"] = "30"
        expanded["LIFECYCLE_CONFIGURATION_STATUS"] = "Enabled"
        if environment == "prod":
            if _bool_non_empty(expanded.get("DATAOFFICE_PROJECT_TYPE")):
                expanded["LIST_IGNORE_TENANTS"] = "0"
            else:
                expanded["LIST_IGNORE_TENANTS"] = "99999,999999"

    fixed_expanded = fix_environment_vars(expanded, environment)

    all_env_setted = True
    return fixed_expanded


def fix_environment_vars(expanded: dict[Any, Any], environment: str) -> dict[Any, Any]:
    """
    Corrige as variáveis de ambiente para remover o prefixo do ambiente virtual, se presente e registra as variáveis de
    ambiente no processo atual.

    Args:
        expanded(dict[Any, Any]) -- dicionário contendo as variáveis de ambiente expandidas
        environment(str) -- nome do ambiente virtual (ex: dev, prod, etc.)

    Returns:
        dict[Any, Any] -- dicionário contendo as variáveis de ambiente corrigidas

    """
    fixed_expanded = {}
    for key, value in expanded.items():
        if key.startswith(f"{environment.upper()}_"):
            fixed_key = key.replace(f"{environment.upper()}_", "")
            fixed_expanded[fixed_key] = value
            print_message(
                f"[Dados] Registrando variável do ambiente virtual - {fixed_key}={value}"
            )

        fixed_expanded[key] = value
    return fixed_expanded


def fix_bucket_names(expanded: dict[Any, Any], environment: str) -> None:
    """
    Corrige os nomes dos buckets adicionando o sufixo do ambiente virtual, se necessário.

    Args:
        expanded(dict[Any, Any]) -- dicionário contendo as variáveis de ambiente expandidas
        environment(str) -- nome do ambiente virtual (ex: dev, prod, etc.)

    Returns:
        None

    """
    if environment != "prod" or _bool_non_empty(
        expanded.get("DATAOFFICE_PROJECT_TYPE")
    ):
        for bucket_var in (
            "WORK_BUCKET_NAME",
            "TRANSIENT_BUCKET_NAME",
            "RECEITA_BUCKET_NAME",
            "ML_SERVICES_BUCKET_NAME",
            "UNPROCESSED_BUCKET_NAME",
            "ERROR_BUCKET_NAME",
            "REPROCESS_BUCKET_NAME",
            "RAW_BUCKET_NAME",
            "STAGE_BUCKET_NAME",
            "ANALYTICS_BUCKET_NAME",
            "RAW_BUCKET_NAME_TENANT",
            "STAGE_BUCKET_NAME_TENANT",
            "ANALYTICS_BUCKET_NAME_TENANT",
            "AUDIT_BUCKET_NAME",
            "FILE_BUCKET_NAME",
        ):
            if (
                bucket_var in expanded
                and expanded[bucket_var]
                and not expanded[bucket_var].endswith(f"-{environment}")
            ):
                expanded[bucket_var] = f"{expanded[bucket_var]}-{environment}"

        input_bucket = expanded.get("INPUT_BUCKET_NAME", "")
        if (
            input_bucket
            and not input_bucket.endswith(f"-{environment}")
            and not input_bucket.endswith(f"-{environment}2")
        ):
            if environment == "dev":
                expanded["INPUT_BUCKET_NAME"] = f"{input_bucket}-{environment}2"
            else:
                expanded["INPUT_BUCKET_NAME"] = f"{input_bucket}-{environment}"


def export_to_process(env: dict[str, str]) -> None:
    """
    Exporta as variáveis de ambiente para o processo atual.

    Args:
        env (dict[str, str]): Dicionário contendo as variáveis de ambiente a serem exportadas.

    Returns:
        None

    """
    os.environ.update(env)


def load_runtime_env(
    *,
    apply_set_all: bool = True,
    configure_aws: bool = True,
    ignore_project_type: bool = False,
) -> dict[str, str]:
    """
    Carrega o ambiente de execução, aplicando as configurações necessárias.

    Args:
        apply_set_all (bool): Indica se todas as variáveis de ambiente devem ser definidas.
        configure_aws (bool): Indica se a configuração do AWS deve ser aplicada.
        ignore_project_type (bool): Indica se o tipo de projeto deve ser ignorado.

    Returns:
        dict[str, str]: Dicionário contendo as variáveis de ambiente carregadas.

    """
    print_message(f"{GREEN}[Dados] Importando configurações de ambiente")

    env = load_data_ci_environment(
        os.environ.copy(), ignore_project_type=ignore_project_type
    )
    print_message(f"sys env: {os.environ}", debug_message=True)
    if apply_set_all:
        env = set_all_env(env)
    if configure_aws:
        from src.datalake.commons.aws_utils import configure_aws_access

        env = configure_aws_access(env)
    export_to_process(env)
    return dict(os.environ)
