"""Contains all environment utils."""

import logging
import os
from collections.abc import Callable
from typing import Any, Literal, overload

from voraus_pipeline_utils.constants import (
    ENV_VAR_DOCKER_REGISTRY,
    ENV_VAR_DOCKER_TAGS,
    ENV_VAR_IMAGE_NAME,
    ENV_VAR_REPOSITORIES,
)

_logger = logging.getLogger(__name__)


def _get_env_or_none(env_var: str | tuple[str, ...], decode: Callable[[str], Any] | None = None) -> Any:  # noqa: ANN401
    """Get an environment variable value or None if not set.

    Supports checking multiple environment variable names (for fallback behavior) or a single variable.
    If multiple variables are provided and more than one is set, a warning is logged and the first
    non-null value is returned.

    Args:
        env_var: A single environment variable name or a tuple of variable names to check.
            If a tuple is provided, variables are checked in order and the first non-null value is returned.
        decode: An optional callable to transform the string value into another type.
            If None, the raw string value is returned.

    Returns:
        The environment variable value (optionally decoded), or None if the variable is not set or empty.
    """
    if isinstance(env_var, tuple):
        results = [_get_env_or_none(variable, decode=decode) for variable in env_var]

        non_null_results = [r for r in results if r is not None]
        result = None if len(non_null_results) == 0 else non_null_results[0]
        if len(non_null_results) > 1:
            _logger.warning(
                "Multiple environment variables from %s are set: %s. Using the first one.",
                env_var,
                non_null_results,
            )

    elif isinstance(env_var, str):
        if (value := os.environ.get(env_var)) not in ["", None]:
            if decode is not None:  # noqa: SIM108
                result = decode(value)  # type: ignore[arg-type]
            else:
                result = value
        else:
            result = None
    return result


def is_github() -> bool:
    """Tries to determine whether the current build is executed on GitHub actions or not.

    Returns:
        True if the current environment is a GitHub Action build, False otherwise.
    """
    github_actions = _get_env_or_none("GITHUB_ACTIONS")
    _logger.debug("GitHub Action build: %s", github_actions)
    return github_actions is not None


def is_jenkins() -> bool:
    """Tries to determine whether the current build is executed on a Jenkins or not.

    Returns:
        True if the current environment is on a Jenkins, False otherwise.
    """
    jenkins_url = _get_env_or_none("JENKINS_URL")
    _logger.debug("Jenkins URL is %s", jenkins_url)
    return jenkins_url is not None


def is_ci() -> bool:
    """Tries to determine whether the current build is executed in a CI pipeline.

    Returns:
        True if the current environment is a CI pipeline, False otherwise.
    """
    ci = _get_env_or_none("CI")
    _logger.debug("CI is %s", ci)
    return ci is not None


def get_git_tag() -> str | None:
    """Returns the tag name if building a tag, else None.

    Returns:
        The git tag from the environment, else None.
    """
    if is_github() and _get_env_or_none("GITHUB_REF_TYPE") == "tag":
        tag = _get_env_or_none("GITHUB_REF_NAME")
    else:
        tag = _get_env_or_none("TAG_NAME")

    _logger.debug("Tag is %s", tag)
    return tag


def get_build_number() -> int | None:
    """Returns the build number if available.

    Returns:
        The build number if set or None.
    """
    build_number = _get_env_or_none(("BUILD_NUMBER", "GITHUB_RUN_NUMBER"), decode=int)
    _logger.debug("Build number is %s", build_number)
    return build_number


def get_branch_name() -> str | None:
    """Returns the branch name.

    Returns:
        The branch name if set or None.
    """
    branch_name = _get_env_or_none(("BRANCH_NAME", "GITHUB_REF_NAME"))
    _logger.debug("Branch name is %s", branch_name)
    return branch_name


def get_change_target_branch_name() -> str | None:
    """Returns the branch name of the change target.

    Returns:
        The name of the branch the pull request points to if this is a pull request, None otherwise.
    """
    change_target = _get_env_or_none(("CHANGE_TARGET", "GITHUB_BASE_REF"))
    _logger.debug("Change target is %s", change_target)
    return change_target


def get_docker_tags_from_env() -> list[str] | None:
    """Returns the docker tag list from an optional environment variable.

    Returns:
        The docker tag list from the environment variable ``ENV_VAR_DOCKER_TAGS`` if existing, else None.
    """
    docker_tags = _get_env_or_none(ENV_VAR_DOCKER_TAGS, lambda tag_str: tag_str.split(","))
    _logger.debug("Docker tags are %s", docker_tags)
    return docker_tags


def get_docker_image_name_from_env() -> str | None:
    """Returns the docker image name from an optional environment variable.

    Returns:
        The docker image name from the environment variable ``ENV_VAR_IMAGE_NAME`` if existing, else None.
    """
    image_name = _get_env_or_none(ENV_VAR_IMAGE_NAME)
    _logger.debug("Docker image name is %s", image_name)
    return image_name


def get_docker_registry_from_env() -> str:
    """Returns the docker registry from an optional environment variable.

    Raises:
        ValueError: If the environment variable is not set.

    Returns:
        The docker registry from the environment variable ``ENV_VAR_DOCKER_REGISTRY``.
    """
    if (registry := _get_env_or_none(ENV_VAR_DOCKER_REGISTRY)) is None:
        error_msg = f"Unable to determine docker registry from environment variable {ENV_VAR_DOCKER_REGISTRY}"
        raise ValueError(error_msg)
    _logger.debug("Docker registry is %s", registry)
    return registry


@overload
def get_docker_repositories_from_env(*, raise_on_none: Literal[True]) -> list[str]: ...


@overload
def get_docker_repositories_from_env(*, raise_on_none: Literal[False]) -> list[str] | None: ...


def get_docker_repositories_from_env(*, raise_on_none: bool = False) -> list[str] | None:
    """Returns the docker repository list from an optional environment variable.

    Args:
        raise_on_none: If True, raises a ValueError if the environment variable is not set.

    Returns:
        The docker repository list from the environment variable ``ENV_VAR_REPOSITORIES`` if existing, else None.

    Raises:
        ValueError: If the environment variable is not set and ``raise_on_none`` is True.
    """
    repositories = _get_env_or_none(ENV_VAR_REPOSITORIES, lambda tag_str: tag_str.split(","))
    if not repositories and raise_on_none:
        error_msg = f"Unable to determine docker repositories from environment variable {ENV_VAR_REPOSITORIES}"
        raise ValueError(error_msg)
    _logger.debug("Docker repositories are %s", repositories)
    return repositories
