"""CLI Utils."""

import json
import re
import shlex
from collections.abc import Collection
from datetime import datetime, timedelta
from json import JSONDecodeError
from pathlib import Path
from typing import Any

import typer
from slices_clientlib_core.base.exceptions import (
    ApiException,
    BadRequestException,
    NotFoundException,
    ServiceException,
    UnauthorizedException,
    ValidationError,
)
from slices_clientlib_core.pubkey import PublicKey

PUBLIC_SSH_KEY_LOCATIONS = ["~/.ssh/id_ed25519.pub", "~/.ssh/id_ecdsa.pub", "~/.ssh/id_rsa.pub"]


def load_public_ssh_keys() -> list[str]:
    """Load SSH keys from well known locations."""
    public_ssh_keys = []
    for ssh_key_location in PUBLIC_SSH_KEY_LOCATIONS:
        path = Path(ssh_key_location).expanduser()
        if path.exists():
            public_ssh_keys.append(path.read_text())

    return public_ssh_keys


local_timezone = datetime.now().astimezone().tzinfo


def shlex_quote_if_needed(arg: str) -> str:
    """Quote for shell, if needed."""
    res = shlex.quote(arg)
    if res == f"'{res}'":
        # simple quoting
        return arg
    return res


def create_add_ssh_pubkey_command(
    base_ssh_command: list[str], pubkeys_to_add: Collection[PublicKey], member_name: str | None
) -> str:
    """Create a command for accessing a node via SSH."""
    pubkey_lines = [f"COMMENT='{member_name or ''}'"]
    for pubkey in pubkeys_to_add:
        pubkey_lines.append(f"PUBKEY='{pubkey.public_key}'")
        pubkey_lines.append(
            'grep -qsF -- "$PUBKEY" ~/.ssh/authorized_keys || echo "$PUBKEY $COMMENT" >> ~/.ssh/authorized_keys'
        )

    escaped_ssh_command = " ".join([shlex_quote_if_needed(a) for a in base_ssh_command])

    lines = "\n".join(pubkey_lines)
    return f"""{escaped_ssh_command} 'sh -s' <<'EOF'\n{lines}\nexit 0\nEOF\n"""


def friendly_datetime(dt: datetime, *, include_seconds: bool = False):
    """Format datetime in local timezone in a human-readable manner."""
    # return dt.astimezone(local_timezone).strftime("%Y-%m-%d %H:%M")
    return dt.astimezone(local_timezone).strftime(
        "%Y-%m-%d %H:%M %Z" if not include_seconds else "%Y-%m-%d %H:%M:%S %Z"
    )


def datetime_with_local_timezone(dt: datetime):
    """Make naive datetime into a datetime with local timezone."""
    if dt.tzinfo is not None:
        raise ValueError("Expected naive datetime object.")
    return dt.astimezone(local_timezone)


def get_error_detail(error_body: Any | None):
    """Get detail from error response."""
    if not isinstance(error_body, str):
        return None
    try:
        error_body_contents = json.loads(error_body)
    except JSONDecodeError:
        return error_body

    error_details = []

    if "detail" in error_body_contents:
        if isinstance(error_body_contents["detail"], list):
            for detail in error_body_contents["detail"]:
                if "msg" in detail:
                    error_details.append(detail["msg"])
                else:
                    error_details.append(str(detail))
        else:
            error_details.append(error_body_contents["detail"])

    if error_details:
        res = []
        for detail in error_details:
            if isinstance(detail, str):
                res.append(detail)
            elif isinstance(detail, dict | list):
                res.append(json.dumps(detail, indent=4))
            else:
                res.append(str(detail))
        return "\n".join(res)

    return "No details available"


def get_api_error_detail(err: ApiException):
    """Show error details for API exceptions."""

    if isinstance(err, ValidationError):
        return "\n".join([f"{error_detail.loc}: {error_detail.msg}" for error_detail in err.details])

    if isinstance(err, BadRequestException):
        return f"Bad Request: {err.detail}"

    if isinstance(err, ServiceException):
        message = "The server encountered an internal error"
        if err._httpx_error:
            message += (
                f" while processing {err._httpx_error.request.method} request "
                f"on {err._httpx_error.request.url}: {err._httpx_status_error.response.text}"
            )
        else:
            message += "."
        message += "\n\nPlease report this issue to support if the issue persists."

        return message

    if isinstance(err, UnauthorizedException):
        return f"Unauthorized: {err._httpx_error}"

    if isinstance(err, NotFoundException):
        assert err._httpx_error
        return f"Resource not found: {err._httpx_error.request.url}"

    return f"TODO: Implement get_api_error_detail for other error types {err} {err!r}"


def is_type_id(s: str, type: str | None = None, domain: str | None = None):
    """Is given string a valid TypeID."""
    type_pattern = type if type else r"[a-z]{1,6}"
    domain_pattern = domain if domain else r"[a-z0-9-.]{2,56}"
    suffix_pattern = r"[01234567][0123456789abcdefghjkmnpqrstvwxyz]{25}"
    pattern = re.compile(f"^{type_pattern}_{domain_pattern}_{suffix_pattern}$")

    return pattern.match(s)


def is_experiment_id(s: str):
    """Is given string a valid experiment TypeID."""
    return is_type_id(s, type="exp")


def is_project_id(s: str):
    """Is given string a valid project TypeID."""
    return is_type_id(s, type="proj")


def validate_type_id(value: str, *, required_type: str | None = None):
    """Validate TypeID."""

    if value is None:
        return None

    if not is_type_id(value):
        raise typer.BadParameter("invalid ID")

    type_, domain, suffix = value.split("_")
    if type_ is not None and type_ != required_type:
        raise typer.BadParameter(f"invalid ID type. Expected {required_type!r}, got {type_!r}")

    return value


def td_format(td_object: timedelta):
    """Generate user-friendly time duration."""

    seconds = int(td_object.total_seconds())

    if seconds == 0:
        return "less than 1 second"

    periods = [
        ("year", 60 * 60 * 24 * 365),
        ("month", 60 * 60 * 24 * 30),
        ("day", 60 * 60 * 24),
        ("hour", 60 * 60),
        ("minute", 60),
        ("second", 1),
    ]
    strings = []
    for period_name, period_seconds in periods:
        if seconds >= period_seconds:
            period_value, seconds = divmod(seconds, period_seconds)
            if period_value == 1:
                strings.append(f"{period_value} {period_name}")
            else:
                strings.append(f"{period_value} {period_name}s")

    return ", ".join(strings)
