from pathlib import Path

from common import exec_command, get_env_variable, get_env_variable_required
from common.graphql_client import GraphqlClient
from common.trivy_helper import scan_image
from src.interface.project import ProjectInterface


class ProjectWithDockerInterface(ProjectInterface):
    def __init__(self):
        super().__init__()
        self.dockerhub_org_name = "seniorsa"
        self.image_name = self._get_image_name()
        self.ci_project_dir = get_env_variable_required("CI_PROJECT_DIR")

    def build_docker_image(self):
        self._build_image()
        self._save_image()

    def trivy_scan_image(self, custom_args=""):
        self._docker_login()
        scan_image(self.image_name, custom_args, self.version)

    def package(self):
        self._push_image()

    @staticmethod
    def _get_image_name() -> str:
        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 _save_image(self):
        image_name = self._get_image_name()
        image_full_name = f"{self.dockerhub_org_name}/{image_name}:{self.version}"

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

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

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

    def _build_image(self):
        self._docker_login()

        valid_platforms = {"linux/amd64", "linux/arm64"}
        docker_from_cache_dir = Path(self.ci_project_dir).joinpath(".cache/temp/docker")
        docker_to_cache_dir = Path(self.ci_project_dir).joinpath(".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(",")
                ]
            )

        self._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 {self.dockerhub_org_name}/{self.image_name}:{self.version} "
                f"-f {sci_dockerfile_path} {sci_docker_build_args} .",
                error_message="Ocorreu uma falha no build da imagem com cache.",
                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 {self.dockerhub_org_name}/{self.image_name}:{self.version} "
                f"-f {sci_dockerfile_path} {sci_docker_build_args} .",
                error_message="Ocorreu uma falha no build da imagem sem cache.",
            )

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

    def _push_image(self):
        self._create_repository()
        self._build_image()

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

    def _create_repository(self):
        ci_project_name = get_env_variable_required("CI_PROJECT_NAME")

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

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

        client = GraphqlClient()

        client.call(mutation, params)

    @staticmethod
    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"
            )
