from pathlib import Path
from typing import Optional, List

import typer

from thestage.cli_command import CliCommand
from thestage.cli_command_helper import get_command_metadata, check_command_permission
from thestage.controllers.utils_controller import validate_config_and_get_service_factory, get_current_directory
from thestage.docker_container.business.container_service import ContainerService
from thestage.docker_container.dto.container_response import DockerContainerDto
from thestage.docker_container.dto.enum.container_pending_action import DockerContainerAction
from thestage.helpers.logger.app_logger import app_logger
from thestage.i18n.translation import __
from thestage.logging.business.logging_service import LoggingService

app = typer.Typer(no_args_is_help=True, help=__("Manage containers"))

@app.command(name='ls', help=__("List containers"), **get_command_metadata(CliCommand.CONTAINER_LS))
def list_containers(
        row: int = typer.Option(
            5,
            '--row',
            '-r',
            help=__("Set number of rows displayed per page"),
            is_eager=False,
        ),
        page: int = typer.Option(
            1,
            '--page',
            '-p',
            help=__("Set starting page for displaying output"),
            is_eager=False,
        ),
        project_public_id: str = typer.Option(
            None,
            '--project-id',
            '-pid',
            help=__("Filter containers by project, using the project's ID"),
            is_eager=False,
        ),
        project_slug: str = typer.Option(
            None,
            '--project-name',
            '-pn',
            help=__("Filter containers by project, using the project's name"),
            is_eager=False,
        ),
        statuses: List[str] = typer.Option(
            None,
            '--status',
            '-s',
            help=__("Filter by status, use --status all to list all containers"),
            is_eager=False,
        ),
):
    command_name = CliCommand.CONTAINER_LS
    app_logger.info(f'Running {command_name}')
    check_command_permission(command_name)

    if sum(v is not None for v in [project_public_id, project_slug]) > 1:
        typer.echo("Provide a single identifier for project - ID or name.")
        raise typer.Exit(1)

    service_factory = validate_config_and_get_service_factory()
    container_service: ContainerService = service_factory.get_container_service()
    container_service.print_container_list(
        row=row,
        page=page,
        project_public_id=project_public_id,
        project_slug=project_slug,
        statuses=statuses,
    )

    typer.echo(__("Containers listing complete"))
    raise typer.Exit(0)


@app.command(name="info", no_args_is_help=True, help=__("Get container info"), **get_command_metadata(CliCommand.CONTAINER_INFO))
def container_info(
        container_public_id: str = typer.Option(
            None,
            '--container-id',
            '-cid',
            help=__("Container ID"),
            is_eager=False,
        ),
        container_slug: str = typer.Option(
            None,
            '--container-name',
            '-cn',
            help=__("Container name"),
            is_eager=False,
        ),
):
    command_name = CliCommand.CONTAINER_INFO
    app_logger.info(f'Running {command_name} from {get_current_directory()}')
    check_command_permission(command_name)

    if sum(v is not None for v in [container_public_id, container_slug]) != 1:
        typer.echo("Provide a single identifier for container - ID or name.")
        raise typer.Exit(1)

    service_factory = validate_config_and_get_service_factory()
    container_service: ContainerService = service_factory.get_container_service()

    container: Optional[DockerContainerDto] = container_service.get_container(
        container_public_id=container_public_id,
        container_slug=container_slug,
    )

    if not container:
        typer.echo("Container not found")
        raise typer.Exit(1)

    typer.echo(__("STATUS: %status%", {'status': str(container.frontend_status.status_translation if container and container.frontend_status else 'UNKNOWN')}))
    typer.echo(__("ID: %slug%", {'slug': str(container.public_id)}))
    typer.echo(__("NAME: %title%", {'title': str(container.slug)}))
    typer.echo(__("IMAGE: %image%", {'image': str(container.docker_image)}))

    typer.echo(f"CONTAINER INSTANCE:")

    if container.instance_rented:
        typer.echo(f"    TYPE: RENTED")
        typer.echo(f"    ID: {container.instance_rented.public_id}")
        typer.echo(f"    NAME: {container.instance_rented.slug}")
        typer.echo(
            __("RENTED INSTANCE STATUS: %instance_status%",
               {'instance_status': str(container.instance_rented.frontend_status.status_translation if container.instance_rented.frontend_status else 'UNKNOWN')})
        )

    if container.selfhosted_instance:
        typer.echo(f"    TYPE: SELF-HOSTED")
        typer.echo(f"    ID: {container.selfhosted_instance.public_id}")
        typer.echo(f"    NAME: {container.selfhosted_instance.slug}")
        typer.echo(
            __("SELF-HOSTED INSTANCE STATUS: %instance_status%",
               {'instance_status': str(container.selfhosted_instance.frontend_status.status_translation if container.selfhosted_instance.frontend_status else 'UNKNOWN')})
        )

    if container.mappings and (container.mappings.port_mappings or container.mappings.directory_mappings):
        if container.mappings.port_mappings:
            typer.echo(__("CONTAINER PORT MAPPING:"))
            for src, dest in container.mappings.port_mappings.items():
                typer.echo(f"    {src} : {dest}")

        if container.mappings.directory_mappings:
            typer.echo(__("CONTAINER DIRECTORY MAPPING:"))
            for src, dest in container.mappings.directory_mappings.items():
                typer.echo(f"    {src} : {dest}")

    raise typer.Exit(0)


@app.command(name="connect", no_args_is_help=True, help=__("Connect to container"), **get_command_metadata(CliCommand.CONTAINER_CONNECT))
def container_connect(
        container_public_id: str = typer.Option(
            None,
            '--container-id',
            '-cid',
            help=__("Container ID"),
            is_eager=False,
        ),
        container_slug: str = typer.Option(
            None,
            '--container-name',
            '-cn',
            help=__("Container name"),
            is_eager=False,
        ),
        username: Optional[str] = typer.Option(
            None,
            '--username',
            '-u',
            help=__("Username for the server instance (required when connecting to self-hosted instance)"),
            is_eager=False,
        ),
        private_ssh_key_path: str = typer.Option(
            None,
            "--private-key-path",
            "-pk",
            help=__("Path to private key that will be accepted by remote server (optional)"),
            is_eager=False,
        ),
):
    command_name = CliCommand.CONTAINER_CONNECT
    app_logger.info(f'Running {command_name} from {get_current_directory()}')
    check_command_permission(command_name)

    if sum(v is not None for v in [container_public_id, container_slug]) != 1:
        typer.echo("Provide a single identifier for container - ID or name.")
        raise typer.Exit(1)

    if private_ssh_key_path and not Path(private_ssh_key_path).is_file():
        typer.echo(f'No file found at provided path {private_ssh_key_path}')
        raise typer.Exit(1)

    service_factory = validate_config_and_get_service_factory()
    container_service: ContainerService = service_factory.get_container_service()

    container_service.connect_to_container(
        container_public_id=container_public_id,
        container_slug=container_slug,
        username=username,
        input_ssh_key_path=private_ssh_key_path,
    )

    app_logger.info(f'Stop connect to container')
    raise typer.Exit(0)


@app.command(name="upload", no_args_is_help=True, help=__("Upload file to container"), **get_command_metadata(CliCommand.CONTAINER_UPLOAD))
def upload_file(
        source_path: str = typer.Argument(help=__("Source file path"),),
        destination: Optional[str] = typer.Argument(help=__("Destination directory path in container. Format: container_id_or_name:/path/to/file"),),
        username: Optional[str] = typer.Option(
            None,
            '--username',
            '-u',
            help=__("Username for the server instance (required when connecting to self-hosted instance)"),
            is_eager=False,
        ),
):
    command_name = CliCommand.CONTAINER_UPLOAD
    app_logger.info(f'Running {command_name} from {get_current_directory()}')
    check_command_permission(command_name)

    service_factory = validate_config_and_get_service_factory()
    container_service: ContainerService = service_factory.get_container_service()

    container_service.put_file_to_container(
        source_path=source_path,
        destination=destination,
        username_param=username,
    )

    app_logger.info(f'File upload completed')
    raise typer.Exit(0)


@app.command(name="download", no_args_is_help=True, help=__("Download file from container"), **get_command_metadata(CliCommand.CONTAINER_DOWNLOAD))
def download_file(
        source: str = typer.Argument(help=__("Source file path in container. Format: container_name:/path/to/file"),),
        destination_path: str = typer.Argument(help=__("Destination directory path on local machine"),),
        username: Optional[str] = typer.Option(
            None,
            '--username',
            '-u',
            help=__("Username for the server instance (required when connecting to self-hosted instance)"),
            is_eager=False,
        ),
):
    command_name = CliCommand.CONTAINER_DOWNLOAD
    app_logger.info(f'Running {command_name} from {get_current_directory()}')
    check_command_permission(command_name)

    service_factory = validate_config_and_get_service_factory()
    container_service: ContainerService = service_factory.get_container_service()

    container_service.get_file_from_container(
        source=source,
        destination_path=destination_path.rstrip("/"),
        username_param=username,
    )

    app_logger.info(f'File download completed')
    raise typer.Exit(0)


@app.command(name="start", no_args_is_help=True, help=__("Start container"), **get_command_metadata(CliCommand.CONTAINER_START))
def start_container(
        container_public_id: str = typer.Option(
            None,
            '--container-id',
            '-cid',
            help=__("Container ID"),
            is_eager=False,
        ),
        container_slug: str = typer.Option(
            None,
            '--container-name',
            '-cn',
            help=__("Container name"),
            is_eager=False,
        ),
):
    command_name = CliCommand.CONTAINER_START
    app_logger.info(f'Running {command_name} from {get_current_directory()}')
    check_command_permission(command_name)

    if sum(v is not None for v in [container_public_id, container_slug]) != 1:
        typer.echo("Provide a single identifier for container - ID or name.")
        raise typer.Exit(1)

    service_factory = validate_config_and_get_service_factory()
    container_service: ContainerService = service_factory.get_container_service()

    container_service.request_docker_container_action(
        container_public_id=container_public_id,
        container_slug=container_slug,
        action=DockerContainerAction.START
    )

    app_logger.info(f'Container is starting')
    raise typer.Exit(0)


@app.command(name="create", no_args_is_help=True, help=__("Create container"), **get_command_metadata(CliCommand.CONTAINER_CREATE))
def create_container(
        project_public_id: Optional[str] = typer.Option(
            None,
            '--project-id',
            '-pid',
            help=__("Project ID. By default, project info is taken from the current directory"),
            is_eager=False,
        ),
        project_slug: Optional[str] = typer.Option(
            None,
            '--project-name',
            '-pn',
            help=__("Project name. By default, project info is taken from the current directory"),
            is_eager=False,
        ),
        working_directory: Optional[str] = typer.Option(
            None,
            "--working-directory",
            "-wd",
            help=__("Full path to working directory"),
            show_default=False,
            is_eager=False,
        ),
        container_slug: Optional[str] = typer.Option(
            None,
            '--container-name',
            '-cn',
            help=__("Container name"),
            is_eager=False,
        ),
        rented_instance_public_id: Optional[str] = typer.Option(
            None,
            '--rented-instance-id',
            '-rid',
            help=__("Rented instance ID on which container will run"),
            is_eager=False,
        ),
        rented_instance_slug: Optional[str] = typer.Option(
            None,
            '--rented-instance-name',
            '-rn',
            help=__("Rented instance name on which container will run"),
            is_eager=False,
        ),
        self_hosted_instance_public_id: Optional[str] = typer.Option(
            None,
            '--self-hosted-instance-id',
            '-sid',
            help=__("Self-hosted instance ID on which container will run"),
            is_eager=False,
        ),
        self_hosted_instance_slug: Optional[str] = typer.Option(
            None,
            '--self-hosted-instance-name',
            '-sn',
            help=__("Self-hosted instance name on which container will run"),
            is_eager=False,
        ),
        image_name: Optional[str] = typer.Option(
            None,
            '--image',
            '-i',
            help=__("Docker image name. If not provided, you will be prompted to select from available images"),
            is_eager=False,
        ),
        gpu_number: Optional[int] = typer.Option(
            None,
            '--gpu-number',
            '-gpun',
            help=__("Number of assigned GPUs for container. No GPU limit by default"),
            is_eager=False,
        ),
        cpu_number: Optional[int] = typer.Option(
            None,
            '--cpu-number',
            '-cpun',
            help=__("Number of assigned CPUs for container. No CPU limit by default"),
            is_eager=False,
        ),
        volume_mounts: Optional[List[str]] = typer.Option(
            None,
            '--volume',
            '-v',
            help=__("Volume mounts. Can be used multiple times. Format: /host_dir:/container_dir"),
            is_eager=False,
        ),
):
    command_name = CliCommand.CONTAINER_CREATE
    app_logger.info(f'Running {command_name} from {get_current_directory()}')
    check_command_permission(command_name)

    if sum(v is not None for v in [project_public_id, project_slug]) > 1:
        typer.echo("Provide a single identifier for project - ID or name.")
        raise typer.Exit(1)

    if sum(v is not None for v in [rented_instance_public_id, rented_instance_slug, self_hosted_instance_public_id, self_hosted_instance_slug]) != 1:
        typer.echo("Provide exactly one identifier (ID or Name) for either a rented or self-hosted instance.")
        raise typer.Exit(1)

    if not container_slug:
        container_slug = typer.prompt("Enter container name")

    volume_mappings_dict = {}
    if volume_mounts:
        for mount in volume_mounts:
            if ":" not in mount:
                typer.echo(f"Invalid volume format: {mount}. Expected host:container")
                raise typer.Exit(1)
            host_path, container_path = mount.split(":", 1)
            volume_mappings_dict[host_path] = container_path

    service_factory = validate_config_and_get_service_factory(working_directory=working_directory)
    container_service: ContainerService = service_factory.get_container_service()

    container_service.create_docker_container(
        project_public_id=project_public_id,
        project_slug=project_slug,
        container_slug=container_slug,
        rented_instance_public_id=rented_instance_public_id,
        rented_instance_slug=rented_instance_slug,
        self_hosted_instance_public_id=self_hosted_instance_public_id,
        self_hosted_instance_slug=self_hosted_instance_slug,
        image_name=image_name,
        cpu_number=cpu_number,
        gpu_number=gpu_number,
        volume_mappings=volume_mappings_dict
    )

    app_logger.info(f'Container is creating')
    raise typer.Exit(0)


@app.command(name="stop", no_args_is_help=True, help=__("Stop container"), **get_command_metadata(CliCommand.CONTAINER_STOP))
def stop_container(
        container_public_id: str = typer.Option(
            None,
            '--container-id',
            '-cid',
            help=__("Container ID"),
            is_eager=False,
        ),
        container_slug: str = typer.Option(
            None,
            '--container-name',
            '-cn',
            help=__("Container name"),
            is_eager=False,
        ),
):
    command_name = CliCommand.CONTAINER_STOP
    app_logger.info(f'Running {command_name} from {get_current_directory()}')
    check_command_permission(command_name)

    if sum(v is not None for v in [container_public_id, container_slug]) != 1:
        typer.echo("Provide a single identifier for container - ID or name.")
        raise typer.Exit(1)

    service_factory = validate_config_and_get_service_factory()
    container_service: ContainerService = service_factory.get_container_service()

    container_service.request_docker_container_action(
        container_public_id=container_public_id,
        container_slug=container_slug,
        action=DockerContainerAction.STOP
    )

    app_logger.info(f'Container is stopping')
    raise typer.Exit(0)


@app.command(name="delete", no_args_is_help=True, help=__("Delete container"), **get_command_metadata(CliCommand.CONTAINER_DELETE))
def delete_container(
        container_public_id: str = typer.Option(
            None,
            '--container-id',
            '-cid',
            help=__("Container ID"),
            is_eager=False,
        ),
        container_slug: str = typer.Option(
            None,
            '--container-name',
            '-cn',
            help=__("Container name"),
            is_eager=False,
        ),
):
    command_name = CliCommand.CONTAINER_DELETE
    app_logger.info(f'Running {command_name} from {get_current_directory()}')
    check_command_permission(command_name)

    if sum(v is not None for v in [container_public_id, container_slug]) != 1:
        typer.echo("Provide a single identifier for container - ID or name.")
        raise typer.Exit(1)

    service_factory = validate_config_and_get_service_factory()
    container_service: ContainerService = service_factory.get_container_service()

    container_service.request_docker_container_action(
        container_public_id=container_public_id,
        container_slug=container_slug,
        action=DockerContainerAction.DELETE
    )

    app_logger.info(f'Container is deleting')
    raise typer.Exit(0)

@app.command(name="restart", no_args_is_help=True, help=__("Restart container"), **get_command_metadata(CliCommand.CONTAINER_RESTART))
def restart_container(
        container_public_id: str = typer.Option(
            None,
            '--container-id',
            '-cid',
            help=__("Container ID"),
            is_eager=False,
        ),
        container_slug: str = typer.Option(
            None,
            '--container-name',
            '-cn',
            help=__("Container name"),
            is_eager=False,
        ),
):
    command_name = CliCommand.CONTAINER_RESTART
    app_logger.info(f'Running {command_name} from {get_current_directory()}')
    check_command_permission(command_name)

    if sum(v is not None for v in [container_public_id, container_slug]) != 1:
        typer.echo("Provide a single identifier for container - ID or name.")
        raise typer.Exit(1)

    service_factory = validate_config_and_get_service_factory()
    container_service: ContainerService = service_factory.get_container_service()

    container_service.request_docker_container_action(
        container_public_id=container_public_id,
        container_slug=container_slug,
        action=DockerContainerAction.RESTART
    )

    app_logger.info(f'Container is restarting')
    raise typer.Exit(0)


@app.command(name="logs", no_args_is_help=True, help=__("Stream real-time Docker container logs or view last logs for a container"), **get_command_metadata(CliCommand.CONTAINER_LOGS))
def container_logs(
        container_public_id: str = typer.Option(
            None,
            '--container-id',
            '-cid',
            help=__("Container ID"),
            is_eager=False,
        ),
        container_slug: str = typer.Option(
            None,
            '--container-name',
            '-cn',
            help=__("Container name"),
            is_eager=False,
        ),
        logs_number: Optional[int] = typer.Option(
            None,
            '--number',
            '-n',
            help=__("Display a number of latest log entries. No real-time stream if provided."),
            is_eager=False,
        ),
):
    command_name = CliCommand.CONTAINER_LOGS
    app_logger.info(f'Running {command_name} from {get_current_directory()}')
    check_command_permission(command_name)

    if sum(v is not None for v in [container_public_id, container_slug]) != 1:
        typer.echo("Provide a single identifier for container - ID or name.")
        raise typer.Exit(1)

    service_factory = validate_config_and_get_service_factory()
    logging_service: LoggingService = service_factory.get_logging_service()

    if logs_number is None:
        logging_service.stream_container_logs_with_controls(
            container_public_id=container_public_id,
            container_slug=container_slug,
        )
    else:
        logging_service.print_last_container_logs(container_public_id=container_public_id, container_slug=container_slug, logs_number=logs_number)

    app_logger.info(f'Container log streaming completed')
    raise typer.Exit(0)