"""BI CLI."""

import functools
from enum import Enum
from typing import Annotated

import typer
from msgspec.json import encode as json_encode
from msgspec.yaml import encode as yaml_encode
from rich.table import Table
from slices_clientlib_bi.bi_image import DiskImageCapability
from slices_clientlib_core.base.exceptions import ApiException, ServiceException
from urllib3.connection import HTTPSConnection
from urllib3.exceptions import MaxRetryError, NewConnectionError

from slices_cli_bi import console, err_console
from slices_cli_bi.util.errors import get_api_error_detail
from slices_cli_bi.util.time import friendly_datetime
from slices_cli_bi.util.type_id import get_type_id_domain

from .bi_context import BIContext

disk_images_app = typer.Typer(no_args_is_help=True)


def handle_some_exceptions(func):
    """Handle specific exceptions by giving the user a useful error message instead of a confusing stacktrace."""

    @functools.wraps(func)
    def wrapper_handle_some_exceptions(*args, **kwargs):
        """Function wrapper for handle_some_exceptions."""  # noqa: D401
        try:
            return func(*args, **kwargs)
        except ServiceException as e:
            err_console.print("[red]There was a connection problem. The service might be down. :zap:")
            err_console.print(f"[red]Details: {e}")
            raise typer.Exit(code=1) from e
        except MaxRetryError as e:
            if isinstance(e.reason, NewConnectionError | HTTPSConnection):
                # example:
                #   NewConnectionError -> [Failed to establish a new connection: Errno 113] No route to host
                #   HTTPSConnection -> Failed to establish a new connection: [Errno 111] Connection refused
                err_console.print("[red]There was a connection problem. The service might be down. :zap:")
                err_console.print(f"[red]Details: {e.reason}")
                raise typer.Exit(code=1) from None
            # If it's something else, re-raise the exception and handle as usually
            raise

    return wrapper_handle_some_exceptions


@disk_images_app.command(name="register", rich_help_panel="Admin Commands")
@handle_some_exceptions
def register_image(
    ctx: BIContext,
    friendly_name: Annotated[str, typer.Option(prompt=True, show_default=False)],
    location: Annotated[str, typer.Option(prompt=True, show_default=False)],
    default_username: Annotated[str, typer.Option(prompt=True, show_default=False)],
    capabilities: Annotated[
        list[DiskImageCapability],
        typer.Option("--capability", "--cap", help="Capabilities of the disk image."),
    ] = [  # noqa: B006 # type: ignore[reportArgumentType]
        DiskImageCapability.PING,
        DiskImageCapability.SSH,
        DiskImageCapability.CLOUD_MINUS_INIT,
        DiskImageCapability.CLOUD_MINUS_INIT_MINUS_PHONEHOME,
    ],
    short_description: Annotated[
        str | None, typer.Option(prompt=True, help="An optional short description of the image.")
    ] = None,
    long_description: Annotated[
        str | None, typer.Option(prompt=True, help="An optional long description of the image.")
    ] = None,
    min_disk: Annotated[
        int | None,
        typer.Option(show_default=False, help="The minimum size of the root disk, in MB."),
    ] = None,
    min_ram: Annotated[
        int | None,
        typer.Option(show_default=False, help="The minimum required amount of RAM for this image, MiB."),
    ] = None,
    tags: Annotated[list[str] | None, typer.Option(show_default=False)] = None,
):
    """Register disk images.

    Only available to admin users.
    """
    try:
        disk_image = (
            ctx.obj.api.for_project(ctx.obj.require_project_id())
            .bi(ctx.obj.infra_id)
            .image.create(
                friendly_name=friendly_name,
                cluster_id=ctx.obj.cluster_id,
                location=location,
                default_username=default_username,
                min_disk_mb=min_disk,
                min_ram_mib=min_ram,
                tags=tags,
                capabilities=capabilities,
                short_description=short_description,
                long_description=long_description,
            )
        )
        console.print(f"[bold green]:cd: Registered {friendly_name} as {disk_image.id} :white_check_mark:")
    except ApiException as err:
        err_console.print("[bold red]Could not register disk image[/] :x:")
        err_console.print(f"[red]{get_api_error_detail(err)}")
        raise typer.Exit(code=1) from None


class ListFormat(Enum):
    """List Formats."""

    table = "table"
    json = "json"
    yaml = "yaml"


def _extract_infa_id(any_type_id: str) -> str:
    return any_type_id.split("_")[1]


class ListImageSort(Enum):
    """List sort options."""

    name = "name"  # pyright: ignore [reportIncompatibleMethodOverride,reportAssignmentType] seems to work fine
    created = "created"


@disk_images_app.command(name="list", rich_help_panel="Commands")
@handle_some_exceptions
def list_images(
    ctx: BIContext,
    format: Annotated[
        ListFormat,
        typer.Option("--format", "-f", help="Format in which the images should be listed."),
    ] = "table",  # type: ignore
    show_tags: Annotated[
        bool,
        typer.Option(
            "--tags/--no-tags",
            "-t/-nt",
            help="Show/hide long description of image. (default: show)",
            rich_help_panel="Table view column selection",
            show_default=False,
        ),
    ] = True,
    show_short_description: Annotated[
        bool,
        typer.Option(
            "--desc/--no-desc",
            "-d/-nd",
            help="Show/hide short description of image. (default: show)",
            rich_help_panel="Table view column selection",
            show_default=False,
        ),
    ] = True,
    show_capabilities: Annotated[
        bool,
        typer.Option(
            "--capabilities/--no-capabilities",
            "-c/-nc",
            help="Show/hide capabilities. (default: hide)",
            rich_help_panel="Table view column selection",
            show_default=False,
        ),
    ] = False,
    show_long_description: Annotated[
        bool,
        typer.Option(
            "--long-desc/--no-long-desc",
            "-l/-nl",
            help="Show/hide long description of image. (default: hide)",
            rich_help_panel="Table view column selection",
            show_default=False,
        ),
    ] = False,
    show_id: Annotated[
        bool,
        typer.Option(
            "--id/--no-id",
            "-i/-ni",
            help="Show/hide the internal ID of the image. (default: hide)",
            rich_help_panel="Table view column selection",
            show_default=False,
        ),
    ] = False,
    show_infra: Annotated[
        bool,
        typer.Option(
            "--infra/--no-infra",
            "-I/-nI",
            help="Show/hide the infrastructure ID of the image. (default: show)",
            rich_help_panel="Table view column selection",
            show_default=False,
        ),
    ] = True,
    sort: Annotated[
        ListImageSort, typer.Option("--sort", "-s", help="Field to sort by (infra_id is always first sort field).")
    ] = "created",  # type: ignore
):
    """List disk images."""
    disk_images = ctx.obj.api.for_project(ctx.obj.require_project_id()).bi(ctx.obj.infra_id).image.list()

    disk_images = list(disk_images)  # fetch all diskimages now

    if sort == ListImageSort.name:
        disk_images.sort(key=lambda i: (get_type_id_domain(i.id), i.friendly_name, i.created_at))
    elif sort == ListImageSort.created:
        disk_images.sort(key=lambda i: (get_type_id_domain(i.id), i.created_at, i.friendly_name))

    if format == ListFormat.table:
        cols = ["Name"]
        if show_infra:
            cols.insert(0, "Infra ID")
        if show_id:
            cols.insert(0, "ID")
        if show_capabilities:
            cols.append("Capabilities")
        cols.append("Created At")
        if show_tags:
            cols.append("Tags")
        if show_short_description:
            cols.append("Description")
        if show_long_description:
            cols.append("Detailed Description")

        table = Table(*cols, title="Images")

        for disk_image in disk_images:
            row = [
                disk_image.friendly_name,
            ]
            if show_infra:
                row.insert(0, _extract_infa_id(disk_image.id))
            if show_id:
                row.insert(0, disk_image.id)
            if show_capabilities:
                row.append(", ".join(sorted(disk_image.capabilities or [])) or "-")

            row.append(
                friendly_datetime(disk_image.created_at) if disk_image.created_at else "-",
            )

            if show_tags:
                row.append(", ".join(disk_image.tags or []) or "-")
            if show_short_description:
                row.append(disk_image.short_description or "-")
            if show_long_description:
                row.append(disk_image.long_description or "-")

            table.add_row(*row)

        console.print(table)

    elif format == ListFormat.json:
        console.print_json(json_encode(list(disk_images)).decode())
    elif format == ListFormat.yaml:
        console.print(yaml_encode(list(disk_images)).decode())
