"""Docker functions"""

import os
import xml.etree.ElementTree as ET
from pathlib import Path

from colorama import Fore

from common import (
    ExitCode,
    exec_command,
    exit_message,
    get_env_variable,
    get_env_variable_required,
    get_version,
    is_path_exist,
    is_semver,
    print_message,
)
from common.file_helpers import get_svc_name
from common.graphql_client import GraphqlClient
from src.enum.project_type import ProjectTypeEnum

DOCKERHUB_ORG_NAME = "seniorsa"


def build_image():
    image_name = get_image_name()

    docker_login()

    valid_platforms = {"linux/amd64", "linux/arm64"}
    docker_from_cache_dir = os.path.join(
        os.environ["CI_PROJECT_DIR"], ".cache/temp/docker"
    )
    docker_to_cache_dir = os.path.join(os.environ["CI_PROJECT_DIR"], ".cache/docker")

    sci_dockerfile_path = get_env_variable("SCI_DOCKERFILE_PATH", "Dockerfile")
    sci_docker_build_args = get_env_variable("SCI_DOCKER_BUILD_ARGS", "")
    sci_docker_platforms = get_env_variable(
        "SCI_DOCKER_PLATFORM", "linux/amd64,linux/arm64"
    )

    sci_docker_platforms = ",".join(
        p for p in sci_docker_platforms.split(",") if p in valid_platforms
    )

    if sci_docker_build_args:
        sci_docker_build_args = " ".join(
            [f"--build-arg {arg.strip()}" for arg in sci_docker_build_args.split(",")]
        )

    print_message(f"Realizando build da a imagem {image_name}")

    _setup_platform(sci_docker_platforms)

    # Existing docker cache is moved to a temporary directory. This is done to avoid
    # issues with build up of invalid cache layers from successive builds.

    docker_from_path = Path(docker_from_cache_dir)
    docker_to_path = Path(docker_to_cache_dir)
    Path.mkdir(docker_from_path, parents=True, exist_ok=True)

    try:
        if not docker_to_path.exists() or not docker_to_path.is_dir():
            raise FileNotFoundError

        files = list(docker_to_path.iterdir())
        if not files:
            raise FileNotFoundError

        for file in files:
            file.rename(docker_from_path / file.name)

    except FileNotFoundError:
        print("No cache found, skipping.")

    try:
        exec_command(
            f"docker buildx build --platform {sci_docker_platforms} "
            f"--cache-from type=local,src={docker_from_cache_dir} "
            f"--cache-to type=local,dest={docker_to_cache_dir},mode=max "
            f"-t {DOCKERHUB_ORG_NAME}/{image_name} "
            f"-f {sci_dockerfile_path} {sci_docker_build_args} .",
            error_message="Ocorreu uma falha no build da imagem.",
            raise_on_error=True,
        )
    except RuntimeError:
        print("Erro ao utilizar cache, tentando sem cache.")
        exec_command(
            f"docker buildx build --platform {sci_docker_platforms} "
            f"-t {DOCKERHUB_ORG_NAME}/{image_name} "
            f"-f {sci_dockerfile_path} {sci_docker_build_args} .",
            error_message="Ocorreu uma falha no build da imagem.",
        )

    exec_command(
        f"rm -rf {docker_from_cache_dir}",
        error_message="Falha ao remover o diretório de cache temporário do docker.",
    )

    save_image()


def push_image(version: str = None):
    image_name = get_image_name()
    sci_project_type = get_env_variable_required("SCI_PROJECT_TYPE")

    build_image()

    docker_login()

    if not version:
        version = get_version()

    project_type = ProjectTypeEnum[sci_project_type]
    if project_type in [
        ProjectTypeEnum.MAVEN_APP,
        ProjectTypeEnum.MAVEN_SDL,
    ] and not is_semver(version):
        version = f"{version}-SNAPSHOT"

    exec_command(
        f"docker tag {DOCKERHUB_ORG_NAME}/{image_name} {DOCKERHUB_ORG_NAME}/{image_name}:{version}"
    )

    create_repository(image_name)

    print(" ")
    print_message(
        f"Fazendo push da imagem {DOCKERHUB_ORG_NAME}/{image_name} e versão {version} para o DockerHub"
    )

    exec_command(
        f"docker push {DOCKERHUB_ORG_NAME}/{image_name}:{version}",
        error_message="Erro ao realizar push da imagem.",
    )

    print(" ")
    print_message(
        f"Versão {version} da {DOCKERHUB_ORG_NAME}/{image_name} enviada ao DockerHub",
        Fore.GREEN,
    )


def create_repositories(pathname: str):
    """
    Cria os repositórios com base nos pom.xml que utilizam o plugin dockerfile-maven-plugin
    """

    images = _enumerate_repositories(pathname)
    for image in images:
        create_repository(image)


def docker_login():
    dockerhub_username = get_env_variable_required("DOCKERHUB_USERNAME")
    dockerhub_pass = get_env_variable_required("DOCKERHUB_PASS")

    result = exec_command(f"docker login -u {dockerhub_username} -p {dockerhub_pass}")

    if result.exit_code == ExitCode.ERROR:
        exit_message("Falha ao realizar o login do docker.")


def get_image_name() -> str:
    if is_path_exist("pom.xml"):
        return get_svc_name()

    ci_project_name = get_env_variable_required("CI_PROJECT_NAME")

    image_name = get_env_variable("SCI_SVC_IMAGE", ci_project_name)
    return (
        image_name.replace("-frontend", "")
        .replace("-backend", "")
        .replace("-mobile", "")
    )


def create_repository(image_name: str):
    ci_project_name = get_env_variable_required("CI_PROJECT_NAME")

    mutation = """
        mutation($repository: String, $gitRepo: String) {
          createDockerhubRepo(repository: $repository, gitRepo: $gitRepo) {
            result
          }
        }
      """

    params = {"repository": image_name, "gitRepo": ci_project_name}

    print_message(f"Criando repositório no DockerHub: {image_name}")

    client = GraphqlClient()

    client.call(mutation, params)


def _enumerate_repositories(pathname: str):
    """
    Lista todas as imagens docker de todos os pom.xml do repositório git
    """

    print_message("Listando os repositórios dockerhub a serem criados")

    docker_image_xpath = "./build/plugins/plugin[artifactId='dockerfile-maven-plugin']/configuration/repository"

    result = []
    source_path = Path(pathname)
    for item in source_path.rglob("pom.xml"):
        if item.is_file():
            imgs = _resolve_xpath(item, docker_image_xpath)
            pom = str(item)
            for img in imgs:
                out = exec_command(
                    f"mvn exec:exec -q -Dexec.executable=echo -Dexec.args='{img}' -f {pom}",
                    False,
                    False,
                )

                print_message(f"Imagem docker encontrada: {out.output}")

                parts = out.output.split("/")
                result.append(parts[1].strip())

    return result


def _resolve_xpath(xml_path: Path, xpath: str):
    xml = xml_path.open()
    root = ET.parse(xml)
    root = root.getroot()
    _strip_namespace(root)
    elements = root.findall(xpath)

    result = []
    for element in elements:
        result.append(element.text)

    return result


def _strip_namespace(element):
    """
    Remove o namespace das tags do XML para simplificar a pesquisa por xpath
    """

    _, has_namespace, postfix = element.tag.partition("}")
    if has_namespace:
        element.tag = postfix
    for child in element:
        _strip_namespace(child)


def save_image():
    image_name = get_image_name()
    print(" ")
    print_message(f"Salvando imagem {DOCKERHUB_ORG_NAME}/{image_name}")

    Path("docker_image").mkdir(parents=True, exist_ok=True)

    exec_command(
        f"docker save -o docker_image/{image_name}.tar {DOCKERHUB_ORG_NAME}/{image_name}"
    )


def load_image(image_name) -> None:
    exec_command(f"docker load -i docker_image/{image_name}.tar")


def _setup_platform(sci_docker_platforms):
    if "arm64" in sci_docker_platforms:
        exec_command(
            "mount binfmt_misc -t binfmt_misc /proc/sys/fs/binfmt_misc && "
            "docker run --rm --privileged tonistiigi/binfmt --install arm64"
        )
