"""Contains all docker utils."""

import json
import logging
from pathlib import Path

from voraus_pipeline_utils.methods.build import sanitize_branch_name
from voraus_pipeline_utils.methods.environment import (
    get_branch_name,
    get_build_number,
    get_docker_image_name_from_env,
    get_docker_registry_from_env,
    get_docker_repositories_from_env,
    get_docker_tags_from_env,
    get_git_tag,
    is_ci,
)
from voraus_pipeline_utils.methods.shell import execute_command

_logger = logging.getLogger(__name__)


def get_tags_from_common_vars(
    tags: list[str] | None = None,
    registry: str | None = None,
    repositories: list[str] | None = None,
    image_name: str | None = None,
) -> list[str]:
    """Parses and returns the docker tags to use from the common environment variable.

    Args:
        tags: The docker tags provided by the CLI.
        registry: The docker registry to use.
        repositories: The docker repositories provided by the CLI.
        image_name: The docker image name provided by the CLI.

    Returns:
        The list of docker image tags to use.
    """
    if tags is not None:
        return tags
    image_name = image_name or get_docker_image_name_from_env()
    repositories = repositories or get_docker_repositories_from_env(raise_on_none=True)
    registry = registry or get_docker_registry_from_env()
    return get_docker_tags_from_env() or get_all_docker_tags(
        image_name=image_name, repositories=repositories, registry=registry
    )


def get_docker_image_tag(
    name: str,
    version_tag: str,
    registry: str,
    target_repo: str = "docker",
) -> str:
    """Constructs a valid docker tag from the given parameters.

    Args:
        name: The name of the image.
        version_tag: The version tag.
        registry: The docker registry to use.
        target_repo: The target repository. Defaults to "docker".

    Returns:
        The docker image tag.
    """
    return f"{registry}/{target_repo}/{name}:{version_tag}"


def get_all_docker_tags(  # noqa: PLR0913
    image_name: str | None,
    repositories: list[str],
    registry: str,
    branch_name: str | None = None,
    build_number: int | None = None,
    tag: str | None = None,
) -> list[str]:
    """Returns a list of all docker tags for the given values / environment.

    Args:
        image_name: The docker image name.
        repositories: A list of all repositories to publish to.
        registry: The docker registry to use.
        branch_name: The branch name. Defaults to None.
        build_number: The build number. Defaults to None.
        tag: The build tag. Defaults to None.

    Raises:
        ValueError: If an invalid combination of input parameters are provided.

    Returns:
        A list of docker images for the given values / environment.
    """
    if len(repositories) == 0:
        msg = "No repositories provided"
        raise ValueError(msg)
    branch_name = branch_name or get_branch_name()
    build_number = build_number or get_build_number()
    tag = tag or get_git_tag()
    version_tags: list[str] = []
    docker_tags: list[str] = []
    if image_name in ["", None]:
        msg = "Invalid image name"
        raise ValueError(msg)
    if branch_name and build_number is None:
        msg = "Build number must be provided when branch name is given"
        raise ValueError(msg)
    if tag:
        version_tags.append(tag)
        version_tags.append("latest")
    elif branch_name:
        sanitized_branch_name = sanitize_branch_name(branch_name=branch_name)
        version_tags.append(f"{sanitized_branch_name}-latest")
        version_tags.append(f"{sanitized_branch_name}-{build_number}")
    else:  # pragma: no cover
        msg = f"Invalid combination of branch_name={branch_name}, build_number={build_number} and tag={tag}"
        raise ValueError(msg)

    for version_tag in version_tags:
        for repository in repositories:
            docker_tags.append(  # noqa: PERF401
                get_docker_image_tag(
                    name=image_name,  # type:ignore [arg-type]  # The image_name can't be None here
                    version_tag=version_tag,
                    target_repo=repository,
                    registry=registry,
                )
            )

    return docker_tags


def docker_build_wrapper(  # noqa: PLR0913
    *,
    dockerfile: Path = Path("Dockerfile"),
    path: Path = Path(),
    tags: list[str] | None = None,
    pull: bool = True,
    use_jfrog: bool | None = None,
    build_args: dict[str, str] | None = None,
) -> None:
    """Wrapper for docker build. Optionally uses the JFrog CLI.

    Args:
        dockerfile: The path to the Dockerfile to use. Defaults to Path("Dockerfile").
        path: The build path. Defaults to Path(".").
        tags: The tags for the docker image. Defaults to None.
        pull: Whether to pull the base image. Defaults to True.
        use_jfrog: If True, the JFrog CLI will be used as additional wrapper.
            If executed in a CI environment, this defaults to True.
        build_args: Additional build arguments for docker.

    """
    build_args = build_args or {}
    tags = tags or []
    use_jfrog = use_jfrog if use_jfrog is not None else is_ci()
    execute_command(
        command=[
            *(["jf"] if use_jfrog else []),
            "docker",
            "build",
            *(["--pull"] if pull else []),
            *[f"-t {tag}" for tag in tags],
            *[f"--build-arg {key}={value}" for key, value in build_args.items()],
            "-f",
            str(dockerfile),
            str(path),
        ]
    )


def docker_push_wrapper(
    *,
    tags: list[str],
    use_jfrog: bool | None = None,
) -> None:
    """Wrapper for docker push. Optionally uses the JFrog CLI.

    Args:
        tags: The tags to push.
        use_jfrog: If True, the JFrog CLI will be used as additional wrapper.
            If executed in a CI environment, this defaults to True.

    """
    use_jfrog = use_jfrog if use_jfrog is not None else is_ci()
    for tag in tags:
        execute_command(command=[*(["jf"] if use_jfrog else []), "docker", "push", tag])


def docker_list_tags_wrapper(*, tags: list[str], file: Path | None = None) -> None:
    """Log and optionally write Docker tags to a file.

    Args:
        tags: List of Docker image tags to be logged and/or written to file.
        file: Optional path to write the tags as JSON.
    """
    _logger.info("The following tags have been generated from the environment variables and/or your input")
    for tag in tags:
        _logger.info("\t- %s", tag)
    if file is not None:
        file.parent.mkdir(parents=True, exist_ok=True)
        file.write_text(json.dumps(tags), encoding="utf-8")
        _logger.info("Tags have been written to file %s", file)


def docker_save_wrapper(*, tags: list[str], output: Path) -> None:
    """Saves the docker images to a file.

    Args:
        tags: List of Docker image tags to save to file.
        output: The output file.
    """
    _logger.info("Saving image(s) %s to file '%s'", tags, output)
    output.parent.mkdir(parents=True, exist_ok=True)
    execute_command(["docker", "save", "-o", str(output), *tags])
