"""BI Infrastructures CLI."""

import functools
import sys
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_core.base.exceptions import ServiceException
from urllib3.connection import HTTPSConnection
from urllib3.exceptions import MaxRetryError, NewConnectionError

from slices_cli_bi import console, err_console

from .bi_context import BIContext

infras_app = typer.Typer(no_args_is_help=True, short_help="Slices Basic Infrastructures.")


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


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

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


def show_tags_default() -> bool:
    """Return the default for --show-tags/--hide-tags.

    This default depends on --all being present. (True if it is, false otherwise)

    This is a very ugly HACK!
    """
    return "--all" in sys.argv or "-a" in sys.argv


@infras_app.command(name="list", rich_help_panel="Commands")
@handle_some_exceptions
def list_infras(
    ctx: BIContext,
    format: Annotated[
        ListFormat,
        typer.Option("--format", "-f", help="Format in which the infrastructures should be listed."),
    ] = "table",  # type: ignore
    refresh: Annotated[
        bool,
        typer.Option("--refresh", "-r", help="Force refresh the infrastructures list", show_default=False),
    ] = False,
    all: Annotated[
        bool,
        typer.Option(
            "--all",
            "-a",
            help="Show all infrastructures.",
            show_default=False,
        ),
    ] = False,
    show_tags: Annotated[
        bool,
        typer.Option(
            "--show-tags/--hide-tags",
            help="Show or hide the tags of infrastructures when --format table. (default: don't show unless --all)",
            show_default=False,
        ),
    ] = show_tags_default(),
):
    """List Slices BI Infrastructures."""
    if refresh:
        refreshed = ctx.obj.api.reload_bi_config()
        if refreshed:
            err_console.print("Note: Successfully refreshed config from online source.")
        else:
            err_console.print(
                "Note: Config is set from the env var $SLICES_CUSTOM_CONFIG. "
                "This means the config is always fresh, and thus not refreshed again."
            )

    infras_by_id = {k: v for k, v in ctx.obj.bi_config.bi_infrastructures.items() if all or "dev" not in v.tags}
    infa_objs = [
        # remove empty tag lists with:
        # {"infra_id": k, **({"tags": v.tags} if v.tags or show_tags else {})}
        {"infra_id": k, "tags": v.tags}
        for k, v in ctx.obj.bi_config.bi_infrastructures.items()
        if all or "dev" not in v.tags
    ]

    if format == ListFormat.table:
        cols = ["ID"]
        if show_tags:
            cols.append("Tags")

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

        for id, infra in infras_by_id.items():
            values = [id]
            if show_tags:
                values.append(", ".join(infra.tags))
            table.add_row(*values)

        console.print(table)

    elif format == ListFormat.json:
        console.print_json(json_encode(list(infa_objs)).decode())
    elif format == ListFormat.yaml:
        console.print("---")
        console.print(yaml_encode(list(infa_objs)).decode())
    elif format == ListFormat.csv:
        # 1 line per infrastructure ID, first column is ID, other columns are tags
        for id, infra in infras_by_id.items():
            if infra.tags or show_tags:
                print(id + "," + ",".join(infra.tags))
            else:
                print(id)
