"""
Contém a implementação da classe DataProject, que representa um projeto de dados em Python, incluindo métodos para
compilação, execução de testes unitários, validação e empacotamento do projeto.

A classe também inclui verificações para arquivos com terminação CRLF e integrações com ferramentas de build e teste
específicas do ecossistema Python. As subclasses DataProcessor, DataPipeline, DataAnalytics e DataMLModel herdam de
DataProject.

"""

import shutil
from pathlib import Path

import boto3

from common import exec_command, get_env_variable, print_message
from src.controller.python_lib import PythonLib
from src.datalake.commons.aws_utils import (
    configure_aws_access,
    make_session,
    sync_directory_to_s3,
)
from src.datalake.commons.data_env import load_runtime_env
from src.datalake.deploy import aws, redshift
from src.interface.python_project import (
    PACKAGE_DIR_CI,
    PYTHON_VENV,
    PythonProjectInterface,
    _get_version,
    build_whl,
    custom_unit_test,
    exec_command_venv,
    fix_pyproject_version,
    read_pyproject_toml,
)


def check_crlf_files() -> None:
    print_message("[Dados] Verificando arquivos CRLF...")
    exts = {
        ".sql",
        ".py",
        ".yaml",
        ".md",
        ".yml",
        ".sh",
        ".json",
        ".properties",
        ".txt",
        ".gitignore",
    }
    ignored_parts = {
        ".git",
        ".venv",
        "senior-ci",
        "data-ci",
        "venv",
        "cache",
        "aws",
        "tmp",
        "temp",
        "target",
    }

    paths_with_crlf = []
    for path in Path(".").rglob("*"):
        if path.is_dir():
            continue
        rel_parts = set(path.parts)
        if rel_parts.intersection(ignored_parts):
            continue
        if path.suffix not in exts and not path.name.endswith("config"):
            continue
        data = path.read_bytes()
        if b"\r\n" in data:
            paths_with_crlf.append(str(path))

    if paths_with_crlf:
        raise RuntimeError(
            f"[Dados] Arquivos com CRLF encontrados: {', '.join(paths_with_crlf)},"
            " revise e corrija, bloqueando fluxo..."
        )


class DataProject(PythonProjectInterface):
    def __init__(self):

        self.__env: dict[str, str] | None = None
        self.__aws_session: boto3.session.Session | None = None

        if Path("pyproject.toml").exists():
            print_message("[Dados] Preparando uv.lock")
            exec_command(f"rm -rf {PYTHON_VENV}")
            exec_command(f"uv venv  {PYTHON_VENV}")
            exec_command(
                "uv sync --compile-bytecode --no-editable", raise_on_error=True
            )

        super().__init__(skip_create_venv=True)

    def _get_env(self) -> dict[str, str]:
        if not self.__env:
            env = load_runtime_env(
                apply_set_all=True, configure_aws=False, ignore_project_type=False
            )
            self.__env = configure_aws_access(env)

        return self.__env

    def _get_aws_session(self) -> boto3.session.Session:
        if not self.__aws_session:
            self.__aws_session = make_session(self._get_env())

        return self.__aws_session

    def compile(self) -> None:
        """
        Gera o artefato do projeto python.
        """
        if self.buildable_project.skip_build():
            return

        project_data = read_pyproject_toml()

        if (
            project_data
            and "project" in project_data
            and "version" in project_data["project"]
            and "build-system" in project_data
        ):
            if (
                get_env_variable("SKIP_PYPROJECT_VERSION_FIX", "false").lower()
                != "true"
            ):
                fix_pyproject_version()

            print_message("python whl prepare using pyproject.toml")
            build_whl(".")

        print_message("validate whl structure")
        exec_command_venv(f"twine check {PACKAGE_DIR_CI}/*")

    def unit_test(self) -> None:
        """
        Executa os testes unitários.
        """
        if self.buildable_project.skip_build():
            return

        if not any(f for f in Path("tests").iterdir() if f.name != ".keep"):
            print_message("[Dados] Pasta tests está vazia, ignorando fluxo...")
            return

        custom_unit_test(source="src", python_path="src")

    def validate(self) -> None:
        """
        Valida o projeto.
        """
        super().validate()

        check_crlf_files()

    def package(self) -> None:
        """
        Empacota o projeto, gerando os artefatos finais.
        """
        PythonLib().package()

    def publish(self) -> None:
        """
        Publica o projeto, disponibilizando os artefatos gerados na AWS.
        """
        aws.deploy(None)

    def deploy(self) -> None:
        """
        Publica as estruturas de dados no redshift, garantindo que as tabelas, views e demais objetos estejam
        atualizados e consistentes com o projeto.
        """
        redshift.publish_redshift()


class DataProcessor(DataProject):

    def publish(self) -> None:

        print_message("[Dados] Preparando lambda python de processor")
        if Path("src/file_processor_fn").exists():
            print_message("[Dados] Gerando zip e enviando para S3")
            zip_path = f"deploy/file_processor_fn-{_get_version()}.zip"
            zip_result = shutil.make_archive(
                f"deploy/file_processor_fn-{_get_version()}",
                "zip",
                "src/file_processor_fn",
            )
            print_message(f"[Dados] ZIP gerado: '{zip_path}' - '{zip_result}'")

            env = self._get_env()

            sync_directory_to_s3(
                "deploy",
                env["WORK_BUCKET_NAME"],
                f"{env['SOURCE_SYSTEM']}/{env.get('TEAM')}/deploy/processor/",
                env,
                delete=True,
                exclude=["*"],
                include=["*.zip"],
            )

        super().publish()


class DataPipeline(DataProject):
    pass


class DataAnalytics(DataProject):
    pass


class DataMLModel(DataProject):
    pass
