"""Basic infrastructure CLI."""

import functools
import socket

import typer
from slices_clientlib_core.base.exceptions import ApiResponseError
from slices_clientlib_core.experiment import Experiment
from urllib3.connection import HTTPSConnection
from urllib3.exceptions import MaxRetryError, NewConnectionError

from . import err_console
from .ai_context import AIContext
from .utils import is_type_id


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 ApiResponseError as e:
            if e._httpx_status_error.response.status_code in (
                502,
                503,
                504,
            ):
                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 None
            # If it's something else, re-raise the exception and handle as usually
            raise
        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


def bi_get_experiment(ctx: AIContext, experiment_id: str) -> Experiment:
    """Get an experiment for a bi command."""
    if is_type_id(experiment_id, type="exp"):
        exp_obj = ctx.obj.api.experiment.get(experiment_id)
    else:
        exp_obj = ctx.obj.api.get_experiment(experiment_id, project_id=ctx.obj.require_project_id())

    if not exp_obj:
        err_console.print(f"Could not retrieve experiment {experiment_id} :x:")
        raise typer.Exit(code=1)
    return exp_obj


def test_ssh_connect(host: str, port: int) -> bool:
    """Check if an SSH connection can be established to the target."""

    # TODO the timeout value is too short. Ideally, it should be based on known RTT of a slices service.

    try:
        with socket.socket(socket.AF_INET6, socket.SOCK_STREAM) as s:
            s.settimeout(0.1)
            if s.connect_ex((host, port)) == 0:
                # if we can connect, return True
                return True
    except:  # noqa: E722 S110
        # ignore IPv6 problems
        pass

    try:
        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
            s.settimeout(0.1)
            return s.connect_ex((host, port)) == 0
    except:  # noqa: E722
        # Any exception means we can't connect.
        # Examples:
        #      gaierror: [Errno -5] No address associated with hostname
        return False
