import json
import re
from pathlib import Path
from typing import List

from colorama import Fore
from gitlab import SEARCH_SCOPE_BLOBS
from jira.resources import Issue
from packaging.version import Version

from argocd.helper import get_deployment_files, get_deployment_files_from_tree
from common import (
    convert_tag_to_semver,
    exec_command,
    exit_message,
    get_commit_tag_as_semver,
    get_env_variable,
    get_env_variable_required,
    print_message,
    repository_is_sdl_flex,
)
from common.file_helpers import get_svc_name
from scripts.create_sre_issue.jira_manager import JiraManager


class Repository:
    _name: str

    version: str
    deployment: str
    deploy_info: List[str]
    deploy_name_custom_field: str
    translations_path: str

    def __init__(self, tag: str, name: str):
        self._name = name

        self.version = tag
        self.deployment = self.get_deployment_name()

        self.deploy_info = [f"{self.deployment}:{self.version}"]

    def get_deployment_name(self) -> str:
        return self._name


class Backend(Repository):
    def __init__(self, tag: str, name: str):
        self.deploy_name_custom_field = "customfield_12001"
        self.translations_path = "java/impl/src/main/resources/translation"

        super().__init__(tag, name)

        if repository_is_sdl_flex():
            self.deploy_info = [
                f"{self.deployment}:{self.version}",
                f"{self.deployment}-http:{self.version}",
            ]

    def get_deployment_name(self) -> str:
        svc_name = get_svc_name()

        if not svc_name:
            return super().get_deployment_name()

        return svc_name


class Frontend(Repository):
    def __init__(self, tag: str, name: str):
        self.deploy_name_custom_field = "customfield_12002"
        self.translations_path = "src/locale"
        self.identifier = self.get_senior_x_identifier()

        super().__init__(tag, name)

    def get_deployment_name(self) -> str:
        package_json_path = Path("package.json")

        if package_json_path.is_file():
            with package_json_path.open("r", encoding="utf-8") as package_data:
                json_data = json.load(package_data)

            app = json_data["project"]["app"]
            domain = json_data["project"]["domain"]
            service = json_data["project"]["service"]

            return f"{app}/{domain}/{service}"

        return super().get_deployment_name()

    def get_senior_x_identifier(self) -> dict:
        package_json_path = Path("package.json")

        if package_json_path.is_file():
            try:
                with package_json_path.open("r", encoding="utf-8") as package_data:
                    json_data = json.load(package_data)

                app = json_data["project"]["app"]
                domain = json_data["project"]["domain"]
                service = json_data["project"]["service"]
            except KeyError:
                error_msg = (
                    "A propriedade 'project' não esta definida no seu package.json "
                    "para mais informações sobre a propriedade acesse a seguinte documentação: "
                    "https://wiki.senior.com.br/pt-br/Plataforma/Frontend-Updater/configuracao-frontend"
                )
                exit_message(error_msg)

        return {"domainName": domain, "serviceName": service, "appName": app}


class ChangelogReader(object):
    def __init__(self, changelog_base_path: str, jira_manager: JiraManager):
        self.jira_manager = jira_manager

        self.issue_pattern = re.compile(
            r"\*?\s*?\[#?(?P<key>\w+-\d+)\](?P<link>\(http[s]?:\/\/[^)]*\))?\s*-?\s*(?P<summary>.*)",
            re.MULTILINE,
        )

        self.changelog_path = f"{changelog_base_path}/CHANGELOG.md"

        tag = get_env_variable_required("CI_COMMIT_TAG")
        project_url = get_env_variable_required("CI_PROJECT_URL")

        self.version = get_commit_tag_as_semver()

        self.changelog_url = (
            f"{project_url}/-/blob/{tag}{self.changelog_path.replace('.','', 1)}"
        )

    def read_issues(self, latest_deployed_version: str = None) -> List[Issue]:
        issues: List[Issue] = []

        tag_section_pattern = re.compile(r"^# [0-9\.]+")

        with open(self.changelog_path, "r", encoding="utf-8") as changelog:
            line = changelog.readline()
            is_in_tag_section = False
            while line:
                if f"# {self.version}\n" == line:
                    is_in_tag_section = True
                    line = changelog.readline()
                    continue

                started_other_section = is_in_tag_section and tag_section_pattern.match(
                    line
                )
                if started_other_section:
                    if not latest_deployed_version:
                        break
                    version_started = line.strip().lstrip("#").strip()
                    latest_version = convert_tag_to_semver(latest_deployed_version)
                    if Version(version_started) <= Version(latest_version):
                        break

                self.extract_issues(issues, line, is_in_tag_section)

                line = changelog.readline()

            changelog.close()

        return issues

    def extract_issues(self, issues, line, is_in_tag_section):
        if is_in_tag_section and self.issue_pattern.match(line):
            try:
                issue_key = self._issue_key_from(line)
                issue = self.jira_manager.get_issue(issue_key)
                issues.append(issue)
            except Exception:
                pass

    def _issue_key_from(self, line: str):
        matches = self.issue_pattern.search(line)
        return matches.group("key")


def find_changed_files(path, latest_deployed_version: str = None) -> List[str]:
    ci_commit_tag = get_env_variable_required("CI_COMMIT_TAG")

    path = Path(path)

    if path.exists():
        tag_or_first_commit = (
            latest_deployed_version
            if latest_deployed_version
            else exec_command(
                "git rev-list HEAD | tail -n 1", print_output=False
            ).output.strip()
        )

        command_return = exec_command(
            f"git diff --name-only {tag_or_first_commit} {ci_commit_tag} | grep {path}",
            print_output=False,
        )

        if command_return.output:
            print_message(f"Arquivos atualizados: \n{command_return.output}")

            return command_return.output.split("\n")

    return []


def get_project_safe(gitlab_manager, manifest_project):
    try:
        return gitlab_manager.projects.get(manifest_project)
    except Exception:
        print_message(
            f"Repositório de manifestos '{manifest_project}' não encontrado.",
            Fore.YELLOW,
        )
        return None


def get_version_from_search(project, image_name, change_image_fn):
    try:
        files_path = project.search(SEARCH_SCOPE_BLOBS, f"{image_name}:")
    except Exception:
        return None
    if not files_path:
        return None

    deployment_files = get_deployment_files(project, files_path)
    return extract_version(deployment_files, image_name, change_image_fn)


def get_version_from_full_scan(project, image_name, change_image_fn):
    try:
        files = project.repository_tree(path="", recursive=True)
    except Exception as error:
        print_message(f"Erro ao listar arquivos: {error}", Fore.RED)
        return None

    deployment_files = get_deployment_files_from_tree(project, files)
    return extract_version(deployment_files, image_name, change_image_fn)


def extract_version(deployment_files, image_name, change_image_fn):
    for deployment_file in deployment_files:
        old_images = []
        change_image_fn(deployment_file.content, "__dry_run__", old_images)
        version = extract_from_images(old_images, image_name)
        if version:
            return version
    return None


def extract_from_images(images, image_name):
    for image in images:
        if image_name in image and ":" in image:
            return image.split(":")[1]
    return None
