import functools
import warnings
from typing import (
    Any,
    Callable,
    Optional,
)

import humanfriendly

from exasol_integration_test_docker_environment.cli.options.docker_repository_options import (
    DEFAULT_DOCKER_REPOSITORY_NAME,
)
from exasol_integration_test_docker_environment.cli.options.system_options import (
    DEFAULT_OUTPUT_DIRECTORY,
)
from exasol_integration_test_docker_environment.cli.options.test_environment_options import (
    LATEST_DB_VERSION,
)
from exasol_integration_test_docker_environment.lib.base.run_task import (
    generate_root_task,
    run_task,
)
from exasol_integration_test_docker_environment.lib.docker.container.utils import (
    remove_docker_container,
)
from exasol_integration_test_docker_environment.lib.docker.networks.utils import (
    remove_docker_networks,
)
from exasol_integration_test_docker_environment.lib.docker.volumes.utils import (
    remove_docker_volumes,
)
from exasol_integration_test_docker_environment.lib.models.api_errors import (
    ArgumentConstraintError,
)
from exasol_integration_test_docker_environment.lib.models.config.build_config import (
    set_build_config,
)
from exasol_integration_test_docker_environment.lib.models.config.docker_config import (
    set_docker_repository_config,
)
from exasol_integration_test_docker_environment.lib.models.data.environment_info import (
    EnvironmentInfo,
)
from exasol_integration_test_docker_environment.lib.test_environment.parameter.docker_db_test_environment_parameter import (
    DbOsAccess,
)
from exasol_integration_test_docker_environment.lib.test_environment.spawn_test_environment_with_docker_db import (
    SpawnTestEnvironmentWithDockerDB,
)
from exasol_integration_test_docker_environment.lib.utils.api_function_decorators import (
    cli_function,
)


def _cleanup(environment_info: EnvironmentInfo) -> None:
    if environment_info.database_info.container_info is not None:
        remove_docker_container(
            [environment_info.database_info.container_info.container_name]
        )
        if environment_info.database_info.container_info.volume_name is not None:
            remove_docker_volumes(
                [environment_info.database_info.container_info.volume_name]
            )
    remove_docker_networks([environment_info.network_info.network_name])


@cli_function
def spawn_test_environment(
    environment_name: str,
    database_port_forward: Optional[int] = None,
    bucketfs_port_forward: Optional[int] = None,
    ssh_port_forward: Optional[int] = None,
    db_mem_size: str = "2 GiB",
    db_disk_size: str = "2 GiB",
    nameserver: tuple[str, ...] = (),
    docker_runtime: Optional[str] = None,
    docker_db_image_version: str = LATEST_DB_VERSION,
    docker_db_image_name: str = "exasol/docker-db",
    db_os_access: Optional[str] = "DOCKER_EXEC",
    create_certificates: bool = False,
    additional_db_parameter: tuple[str, ...] = (),
    docker_environment_variable: tuple[str, ...] = (),
    accelerator: tuple[str, ...] = (),
    source_docker_repository_name: str = DEFAULT_DOCKER_REPOSITORY_NAME,
    source_docker_tag_prefix: str = "",
    source_docker_username: Optional[str] = None,
    source_docker_password: Optional[str] = None,
    target_docker_repository_name: str = DEFAULT_DOCKER_REPOSITORY_NAME,
    target_docker_tag_prefix: str = "",
    target_docker_username: Optional[str] = None,
    target_docker_password: Optional[str] = None,
    output_directory: str = DEFAULT_OUTPUT_DIRECTORY,
    temporary_base_directory: str = "/tmp",
    workers: int = 5,
    task_dependencies_dot_file: Optional[str] = None,
    log_level: Optional[str] = None,
    use_job_specific_log_file: bool = False,
    bucketfs_http_port_forward: Optional[int] = None,
    bucketfs_https_port_forward: Optional[int] = None,
) -> tuple[EnvironmentInfo, Callable[[], None]]:
    """
    This function spawns a test environment with a docker-db container and a connected test-container.
    The test-container is reachable by the database for output redirects of UDFs.
    The function returns an environment_info object, describing the environment, and a cleanup-method, which
    can be used to stop the environment.
    raises: TaskRuntimeError if spawning the test environment fails

    """

    def str_or_none(x: Any) -> Optional[str]:
        return str(x) if x is not None else None

    parsed_db_mem_size = humanfriendly.parse_size(db_mem_size)
    if parsed_db_mem_size < humanfriendly.parse_size("1 GiB"):
        raise ArgumentConstraintError("db_mem_size", "needs to be at least 1 GiB")
    parsed_db_disk_size = humanfriendly.parse_size(db_disk_size)
    if parsed_db_disk_size < humanfriendly.parse_size("100 MiB"):
        raise ArgumentConstraintError("db_disk_size", "needs to be at least 100 MiB")
    db_os_access_value = (
        DbOsAccess[db_os_access] if db_os_access else DbOsAccess.DOCKER_EXEC
    )
    if len(accelerator) > 0 and accelerator != ("nvidia=all",):
        raise ArgumentConstraintError(
            "accelerator", "Only value 'nvidia=all' is supported"
        )
    if bucketfs_port_forward is not None:
        warnings.warn(
            "Parameter 'bucketfs_port_forward' is deprecated. Use 'bucketfs_http_port_forward' instead.",
            DeprecationWarning,
        )

    set_build_config(
        False,
        (),
        False,
        False,
        output_directory,
        temporary_base_directory,
        None,
        None,
    )
    set_docker_repository_config(
        source_docker_password,
        source_docker_repository_name,
        source_docker_username,
        source_docker_tag_prefix,
        "source",
    )
    set_docker_repository_config(
        target_docker_password,
        target_docker_repository_name,
        target_docker_username,
        target_docker_tag_prefix,
        "target",
    )
    task_creator = lambda: generate_root_task(
        task_class=SpawnTestEnvironmentWithDockerDB,
        environment_name=environment_name,
        database_port_forward=str_or_none(database_port_forward),
        bucketfs_port_forward=str_or_none(bucketfs_port_forward),
        bucketfs_http_port_forward=str_or_none(bucketfs_port_forward),
        bucketfs_https_port_forward=str_or_none(bucketfs_https_port_forward),
        ssh_port_forward=str_or_none(ssh_port_forward),
        mem_size=db_mem_size,
        disk_size=db_disk_size,
        nameservers=nameserver,
        docker_runtime=docker_runtime,
        docker_db_image_version=docker_db_image_version,
        docker_db_image_name=docker_db_image_name,
        db_os_access=db_os_access_value,
        db_user="sys",
        db_password="exasol",
        bucketfs_write_password="write",
        no_test_container_cleanup_after_success=True,
        no_test_container_cleanup_after_failure=False,
        no_database_cleanup_after_success=True,
        no_database_cleanup_after_failure=False,
        create_certificates=create_certificates,
        test_container_content=None,
        additional_db_parameter=additional_db_parameter,
        docker_environment_variables=docker_environment_variable,
        accelerator=accelerator,
    )
    environment_info = run_task(
        task_creator,
        workers,
        task_dependencies_dot_file,
        log_level=log_level,
        use_job_specific_log_file=use_job_specific_log_file,
    )
    return environment_info, functools.partial(_cleanup, environment_info)
