import json
import mimetypes
import os
import re
from pathlib import Path

import boto3
from colorama import Fore

from common import (
    ExitCode,
    exec_command,
    exit_message,
    get_env_variable,
    get_env_variable_required,
    get_version,
    print_message,
)
from common.sonar_helper import SonarHelper
from src.interface.project import ProjectInterface


class AngularProjectInterface(ProjectInterface):
    def __init__(self) -> None:
        super().__init__()

        self.ci_project_name = get_env_variable_required("CI_PROJECT_NAME")

        if self.buildable_project.skip_build():
            return

        self.package_json = self._get_package_json()

        if not self.package_json.is_file():
            exit_message(
                "O projeto está usando o script de Angular porém não possui o package.json"
            )

        self.sci_deploy_cdn = get_env_variable("DEPLOY_CDN")
        self.sci_frontend_bucket_acl = get_env_variable("SCI_FRONTEND_BUCKET_ACL")

        self._update_npmrc()

        self.version = get_version()

    def validate(self):
        super().validate()

        if self.buildable_project.skip_build():
            return

        self._install()
        self._execute_npm_command("lint --if-present")

    def compile(self):
        if self.buildable_project.skip_build():
            return

        self._install()
        self._build()

    def unit_test(self):
        if self.buildable_project.skip_build():
            return

        self._install()
        self._execute_npm_command("test")

    def sonar_scanner(self):
        sonar_helper = SonarHelper(
            skip_build=self.buildable_project.skip_build(),
        )
        sonar_helper.scanner_analyze()

    def _build(self):
        self._execute_npm_command("build --production")

    def _install(self):
        package_lock_path = Path("package-lock.json")

        if not package_lock_path.exists():
            print_message("package-lock.json not found. Running 'npm install'.")
            exec_command("npm install")
        exec_command("npm ci --cache .npm --prefer-offline")

    def _get_package_json_attribute(self, attribute: str) -> str:
        try:
            with self.package_json.open(encoding="utf-8") as file:
                json_data = json.load(file)
            return json_data.get(attribute)
        except Exception as err:
            print_message(err, Fore.RED)
            exit_message(
                f"Não foi possível obter a propriedade {attribute} do package.json."
            )

    def _get_application_details(self):
        details_to_get = ["app", "domain", "service", "serviceDependencies"]
        details_to_return = []
        project_data = self._get_package_json_attribute("project")
        for detail in details_to_get:
            details_to_return.append(project_data.get(detail))

        return details_to_return

    def _execute_npm_command(self, command):
        command_result = exec_command(f"npm run {command}")

        if command_result.exit_code == ExitCode.ERROR:
            exit_message("Node command failed. Exiting...")

        return command_result.output

    def _package_to_cdn(self):
        sci_cdn_role_arn = get_env_variable("SCI_CDN_ROLE_ARN")
        sci_cdn_bucket_name = get_env_variable("SCI_CDN_BUCKET_NAME")
        sci_cdn_bucket_path = get_env_variable("SCI_CDN_BUCKET_PATH")
        sci_cdn_distribution_id = get_env_variable("SCI_CDN_DISTRIBUTION_ID")

        bucket_path = f"{sci_cdn_bucket_path}/{self.version}"

        if not sci_cdn_bucket_path:
            app_name, domain, service, _ = self._get_application_details()

            bucket_path = f"{app_name}/{domain}/{service}/{self.version}"

        if get_env_variable("AWS_ACCESS_KEY_ID"):
            del os.environ["AWS_ACCESS_KEY_ID"]
        if get_env_variable("AWS_SECRET_ACCESS_KEY"):
            del os.environ["AWS_SECRET_ACCESS_KEY"]

        sts_client = boto3.client("sts")
        assumed_role = sts_client.assume_role(
            RoleArn=sci_cdn_role_arn, RoleSessionName="CDNUploader"
        )
        credentials = assumed_role["Credentials"]

        s3_client = boto3.client(
            "s3",
            aws_access_key_id=credentials["AccessKeyId"],
            aws_secret_access_key=credentials["SecretAccessKey"],
            aws_session_token=credentials["SessionToken"],
        )

        print_message(f"Enviando dist para o S3 de CDN: {sci_cdn_bucket_name}")

        dist_path = self._get_dist_path()
        extra_args = {}

        for path in dist_path.rglob("*"):
            if path.is_file():
                mimetype, _ = mimetypes.guess_type(path)

                if mimetype:
                    extra_args["ContentType"] = mimetype

                s3_client.upload_file(
                    str(path),
                    sci_cdn_bucket_name,
                    f"{bucket_path}/{str(path.relative_to(dist_path))}",
                    ExtraArgs=extra_args,
                )

        if sci_cdn_distribution_id:
            cloudfront_client = boto3.client(
                "cloudfront",
                aws_access_key_id=credentials["AccessKeyId"],
                aws_secret_access_key=credentials["SecretAccessKey"],
                aws_session_token=credentials["SessionToken"],
            )

            print_message("Invalidando CDN")

            cloudfront_client.create_invalidation(
                DistributionId=sci_cdn_distribution_id,
                InvalidationBatch={
                    "Paths": {"Quantity": 1, "Items": [f"/{bucket_path}*"]},
                    "CallerReference": str(hash(f"/{bucket_path}*")),
                },
            )

    def _update_npmrc(self):
        npmrc_path = Path(".npmrc")
        if npmrc_path.is_file():
            content = npmrc_path.read_text(encoding="utf-8")
            updated_content = re.sub(
                r"maven\.senior\.com\.br/artifactory/api/npm/npm",
                "nexus.senior.com.br/repository/npm",
                content,
            )
            npmrc_path.write_text(updated_content, encoding="utf-8")

    def _get_package_json(self):
        return Path("package.json")

    def _get_dist_path(self):
        dist_base = Path("dist")

        if not dist_base.exists():
            exit_message("Pasta dist/ não encontrada após o build")

        angular_version = self._get_angular_version()
        if angular_version and angular_version >= 18:
            project_dist = dist_base / self.ci_project_name / "browser"
            if project_dist.exists():
                return project_dist

        project_dist = dist_base / self.ci_project_name
        if project_dist.exists():
            return project_dist

        return dist_base

    def _get_angular_version(self):
        try:
            dependencies = self._get_package_json_attribute("dependencies") or {}
            dev_dependencies = self._get_package_json_attribute("devDependencies") or {}

            angular_core = dependencies.get("@angular/core") or dev_dependencies.get(
                "@angular/core"
            )

            if angular_core:
                match = re.search(r"(\d+)", angular_core)
                if match:
                    return int(match.group(1))
        except Exception:
            pass

        return None
