"""
Utilitários para scripts Python do Data CI, incluindo execução de comandos shell, carregamento de variáveis de ambiente
a partir de arquivos e manipulação de strings.
"""

from __future__ import annotations

import os
import re
import subprocess
from pathlib import Path
from typing import Iterable

from common import print_message

RED = "\033[31m"
GREEN = "\033[32m"
BLUE = "\033[34m"
MAGENTA = "\033[35m"
CYAN = "\033[36m"
RESET = "\033[0m"


VAR_ASSIGNMENT = re.compile(r"^([A-Za-z_][\w]*)=(.*)$")
VAR_INTERPOLATION = re.compile(r"\$\{([A-Za-z_][\w]*)\}")


def run_cmd(
    args: list[str],
    *,
    env: dict[str, str] | None = None,
    check: bool = True,
    capture_output: bool = False,
    cwd: Path | None = None,
    timeout: int | None = None,
) -> subprocess.CompletedProcess[str]:
    """
    Executa um comando shell com as opções fornecidas.

    Args:
        args (list[str]): Lista de argumentos do comando a ser executado.
        env (dict[str, str] | None): Dicionário de variáveis de ambiente a serem usadas durante a execução do comando.
            Se None, usa o ambiente atual.
        check (bool): Se True, levanta uma exceção se o comando retornar um código de saída diferente de zero.
        capture_output (bool): Se True, captura a saída padrão e o erro padrão do comando.
        cwd (Path | None): Diretório de trabalho atual para a execução do comando.
        timeout (int | None): Tempo limite em segundos para a execução do comando. Se None, não há limite de tempo.

    Returns:
        subprocess.CompletedProcess[str]: Objeto contendo informações sobre a execução do comando, incluindo código de
            saída, saída padrão e erro padrão.

    Raises:
        subprocess.CalledProcessError: Se check for True e o comando retornar um código de saída diferente de zero.
        subprocess.TimeoutExpired: Se o comando exceder o tempo limite especificado.

    """
    return subprocess.run(
        args,
        env=env,
        check=check,
        text=True,
        capture_output=capture_output,
        cwd=str(cwd) if cwd else None,
        timeout=timeout,
    )


def _strip_quotes(value: str) -> str:
    """
    Remove aspas simples ou duplas do início e do fim de uma string, se presentes.

    Args:
        value (str): A string da qual remover as aspas.

    Returns:
            str: A string sem aspas no início e no fim, se presentes.

    """
    value = value.strip()
    if len(value) >= 2 and (
        (value[0] == '"' and value[-1] == '"') or (value[0] == "'" and value[-1] == "'")
    ):
        return value[1:-1]
    return value


def _interpolate(value: str, variables: dict[str, str]) -> str:
    """
    Interpola variáveis em uma string usando o dicionário fornecido e as variáveis de ambiente.

    Args:
        value (str): A string na qual realizar a interpolação.
        variables (dict[str, str]): Dicionário de variáveis a serem usadas na interpolação.

    Returns:
        str: A string interpolada.

    """

    def replace(match: re.Match[str]) -> str:
        key = match.group(1)
        return variables.get(key, os.environ.get(key, ""))

    return VAR_INTERPOLATION.sub(replace, value)


def parse_shell_kv_file(path: Path, variables: dict[str, str]) -> dict[str, str]:
    """
    Analisa um arquivo de variáveis de ambiente no formato shell e retorna um dicionário de variáveis.

    Args:
        path (Path): Caminho para o arquivo de variáveis de ambiente.
        variables (dict[str, str]): Dicionário de variáveis a serem usadas na interpolação.

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

    """
    result: dict[str, str] = {}
    if not path.exists():
        return result

    for raw_line in path.read_text(encoding="utf-8").splitlines():
        line = raw_line.strip()
        if not line or line.startswith("#"):
            continue

        if line.startswith("export "):
            line = line[len("export ") :].strip()

        match = VAR_ASSIGNMENT.match(line)
        if not match:
            continue

        key, raw_value = match.groups()
        value = _strip_quotes(raw_value)
        value = _interpolate(value, {**variables, **result})
        result[key] = value

    return result


def load_env_from_files(
    files: Iterable[Path],
    *,
    base_env: dict[str, str] | None = None,
    system_vars: bool = False,
) -> dict[str, str]:
    """
    Carrega variáveis de ambiente a partir de uma lista de arquivos, mesclando-as com as variáveis de ambiente
    existentes.

    Args:
        files (Iterable[Path]): Lista de caminhos para os arquivos de variáveis de ambiente.
        base_env (dict[str, str] | None): Dicionário de variáveis de ambiente base a serem usadas como referência.
            Se None, usa o ambiente atual.
        system_vars (bool): Se True, carrega variáveis padrão adicionais.

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

    """
    env = dict(base_env or os.environ)
    for file_path in files:
        if system_vars:
            print_message(
                f"[Dados] Carregando variáveis de ambiente do arquivo: {file_path}",
                debug_message=True,
            )
        else:
            print_message(
                f"[Dados] Carregando variáveis do projeto do arquivo: {file_path}"
            )
        temp_env = parse_shell_kv_file(file_path, env)
        print_message(
            f"[Dados] Variáveis carregadas do arquivo {file_path}: {temp_env}",
            debug_message=True,
        )
        env.update(temp_env)
    return env


def require_env(env: dict[str, str], name: str) -> str:
    """
    Obtém o valor de uma variável de ambiente obrigatória a partir do dicionário fornecido.

    Args:
        env (dict[str, str]): Dicionário contendo as variáveis de ambiente.
        name (str): Nome da variável de ambiente obrigatória.

    Returns:
        str: Valor da variável de ambiente.

    Raises:
        RuntimeError: Se a variável de ambiente obrigatória estiver ausente.

    """
    value = env.get(name, "").strip()
    if not value:
        raise RuntimeError(f"[Dados] Variável obrigatória ausente: {name}")
    return value


def extract_queries_from_file(
    env: dict[str, str], sql_file: Path, data_type: str
) -> list[tuple[str, str]]:
    """
    Extrai consultas SQL de um arquivo, substituindo tokens de ambiente e retornando uma lista de tuplas contendo o nome
    da tabela e a consulta SQL correspondente.

    Args:
        env (dict[str, str]): Dicionário contendo as variáveis de ambiente.
        sql_file (Path): Caminho para o arquivo SQL.
        data_type (str): Tipo de dados (por exemplo, "tenant").

    Returns:
        list[tuple[str, str]]: Lista de tuplas contendo o nome da tabela e a consulta SQL correspondente.

    """
    queries: list[tuple[str, str]] = []
    buffer = ""
    counter = 1

    tenant_sufix = ""
    if data_type == "tenant":
        tenant_sufix = "_tenant"

    for raw_line in sql_file.read_text(encoding="utf-8").splitlines():
        line = raw_line.strip()
        if line.startswith("--") or not line:
            continue
        line = (
            line.replace("${TYPE}", data_type)
            .replace("${ENVIRONMENT}", env.get("ENVIRONMENT", "").upper())
            .replace("${TENANT}", tenant_sufix)
        )
        buffer += f"{line} "
        if line.endswith(";"):
            queries.append((f"{sql_file}_{counter}", buffer.strip()))
            counter += 1
            buffer = ""
    return queries
