"""Shared utilities and constants for CLI commands."""

from __future__ import annotations

import json
from datetime import datetime, timezone
from enum import Enum
from pathlib import Path
from typing import Any

import typer
from rich.console import Console
from rich.table import Table

from .client import QrmClientError
from .config import load_login_state, LoginState

DEFAULT_BASE_URL = "https://example.com"
DEFAULT_CONFIG_PATH = Path.home() / ".config" / "qrm" / "login.json"

API_DATE_FORMAT = "%Y-%m-%d %H:%M:%S"
CLI_DATE_FORMATS = [
    API_DATE_FORMAT,
    "%Y-%m-%dT%H:%M:%SZ",
    "%Y-%m-%dT%H:%M:%S",
    "%Y-%m-%dT%H:%M:%S%z",
]
EPOCH_START = datetime(1970, 1, 1, tzinfo=timezone.utc)

console = Console()


class OutputFormat(str, Enum):
    rich = "rich"
    json = "json"


def resolve_config_path(config_path: Path | None) -> Path:
    """Resolve the configuration path or use the default."""
    return config_path.expanduser() if config_path else DEFAULT_CONFIG_PATH


def load_state_or_exit(config_path: Path | None) -> LoginState:
    """Load login state from config or exit with error."""
    try:
        return load_login_state(resolve_config_path(config_path))
    except FileNotFoundError:
        typer.secho(
            "No login state found. Please run `qrm login` first or provide --config-path.",
            fg=typer.colors.RED,
        )
        raise typer.Exit(code=1)
    except Exception as exc:  # pragma: no cover - defensive guard
        typer.secho(f"Could not read login state: {exc}", fg=typer.colors.RED)
        raise typer.Exit(code=1) from exc


def emit(payload: Any, output: OutputFormat) -> None:
    """Emit output in the specified format."""
    if output is OutputFormat.json:
        typer.echo(json.dumps(payload))
        return

    if isinstance(payload, str):
        console.print(payload)
        return

    if isinstance(payload, dict):
        table = Table(show_header=True, header_style="bold")
        table.add_column("Field", style="cyan", no_wrap=True)
        table.add_column("Value", style="green")
        for key, value in payload.items():
            table.add_row(
                str(key), json.dumps(value) if isinstance(value, (dict, list)) else str(value)
            )
        console.print(table)
        return

    console.print(payload)


def resolve_uut_window(
    start: datetime | None,
    stop: datetime | None,
) -> tuple[datetime, datetime]:
    """Resolve the time window for a UUT status query."""
    return resolve_window(
        start, stop, default_start=EPOCH_START, default_stop=datetime.now(timezone.utc)
    )


def resolve_box_window(
    start: datetime | None,
    stop: datetime | None,
) -> tuple[datetime, datetime]:
    """Resolve the time window for a box list query."""
    return resolve_window(start, stop, default_start=EPOCH_START)


def resolve_window(
    start: datetime | None,
    stop: datetime | None,
    *,
    default_start: datetime,
    default_stop: datetime | None = None,
) -> tuple[datetime, datetime | None]:
    """Validate and resolve a start/stop time window."""
    stop_value = stop or default_stop
    start_value = start or default_start
    if stop_value and start_value > stop_value:
        raise typer.BadParameter("--start must be earlier than --stop")
    return start_value, stop_value


def format_api_datetime(value: datetime | None) -> str | None:
    """Format a datetime for the API."""
    if value is None:
        return None
    if value.tzinfo is None:
        value = value.replace(tzinfo=timezone.utc)
    return value.astimezone(timezone.utc).strftime(API_DATE_FORMAT)


def render_uut_status(payload: Any) -> Table:
    """Render UUT status results as a table."""
    table = Table(show_header=True, header_style="bold")
    table.add_column("Result Set", style="cyan")
    table.add_column("Serial", style="cyan")
    table.add_column("Outcome", style="green")
    table.add_column("Start", style="magenta")
    table.add_column("Stop", style="magenta")
    table.add_column("Station", style="yellow")
    table.add_column("Operator", style="yellow")

    rows = payload if isinstance(payload, list) else ([payload] if payload else [])

    if not rows:
        table.add_row("No results", "-", "-", "-", "-", "-", "-")
        return table

    for entry in rows:
        if not isinstance(entry, dict):
            table.add_row(str(entry), "-", "-", "-", "-", "-", "-")
            continue

        info = entry.get("Info") if isinstance(entry.get("Info"), dict) else {}
        table.add_row(
            str(entry.get("ResultSetName") or entry.get("Id", "-")),
            str(info.get("UutSerialNumber") or entry.get("SerialNumber", resolve_serial(entry))),
            str(entry.get("Outcome", "-")),
            format_timestamp(entry.get("StartDateTime")),
            format_timestamp(entry.get("StopDateTime")),
            str(info.get("StationName") or "-"),
            str(info.get("Operator") or "-"),
        )

    return table


def render_box_list(payload: Any) -> Table:
    """Render box list results as a table."""
    table = Table(show_header=True, header_style="bold")
    table.add_column("Box", style="cyan")
    table.add_column("Shipped", style="green")
    table.add_column("Units", style="blue")
    table.add_column("Start", style="magenta")
    table.add_column("Finished", style="magenta")
    table.add_column("Location", style="yellow")

    rows = payload if isinstance(payload, list) else ([payload] if payload else [])

    if not rows:
        table.add_row("No results", "-", "-", "-", "-", "-")
        return table

    for entry in rows:
        if not isinstance(entry, dict):
            table.add_row(str(entry), "-", "-", "-", "-", "-")
            continue

        table.add_row(
            str(entry.get("BoxIdentifier") or entry.get("BoxId") or entry.get("Id", "-")),
            str(
                entry.get("Shipped", "-")
                if entry.get("Shipped") is not None
                else entry.get("Status") or entry.get("Outcome", "-")
            ),
            str(entry.get("NumberOfUnits") or "-"),
            format_timestamp(entry.get("StartedDateTime") or entry.get("StartDateTime")),
            format_timestamp(entry.get("FinishedDateTime") or entry.get("StopDateTime")),
            str(entry.get("Location") or entry.get("StationName") or "-"),
        )

    return table


def render_box_detail(payload: Any, content: Any = None) -> Table:
    """Render box details in horizontal table format matching box_list."""
    table = Table(show_header=True, header_style="bold")
    table.add_column("Box", style="cyan")
    table.add_column("Shipped", style="green")
    table.add_column("Units", style="blue")
    table.add_column("Start", style="magenta")
    table.add_column("Finished", style="magenta")

    if isinstance(payload, dict):
        box_id = payload.get("boxIdentifier") or payload.get("BoxIdentifier") or "-"
        shipped = payload.get("shipped") or payload.get("Shipped")
        num_units = payload.get("numberOfUnits") or payload.get("NumberOfUnits")
        started = payload.get("startedDateTime") or payload.get("StartedDateTime")
        finished = payload.get("finishedDateTime") or payload.get("FinishedDateTime")

        table.add_row(
            str(box_id),
            str(shipped if shipped is not None else "-"),
            str(num_units if num_units is not None else "-"),
            format_timestamp(started),
            format_timestamp(finished),
        )
    else:
        if payload:
            table.add_row(str(payload), "-", "-", "-", "-")

    return table


def render_uut_table(content_list: list[Any]) -> Table:
    """Render UUT contents in table format aligned with box detail table."""
    table = Table(show_header=True, header_style="bold")
    table.add_column("Serial Number", style="cyan")
    table.add_column("Type", style="magenta")
    table.add_column("Identifier Tag", style="yellow")
    table.add_column("", style="green")  # Hidden column for width alignment
    table.add_column("", style="blue")  # Hidden column for width alignment

    for uut in content_list:
        if isinstance(uut, dict):
            serial = str(uut.get("serialNumber") or uut.get("SerialNumber") or "-")
            uut_type = str(uut.get("uutTypeName") or uut.get("UutTypeName") or "-")
            tag = str(uut.get("identifierTag") or uut.get("IdentifierTag") or "-")
            table.add_row(serial, uut_type, tag, "", "")
        else:
            table.add_row(str(uut), "-", "-", "", "")

    return table


def render_results_status(payload: Any) -> Table:
    """Render full test results as a table with all columns."""
    table = Table(show_header=True, header_style="bold")
    table.add_column("Result Set", style="cyan")
    table.add_column("Serial", style="cyan")
    table.add_column("Outcome", style="green")
    table.add_column("Start", style="magenta")
    table.add_column("Stop", style="magenta")
    table.add_column("Station", style="yellow")
    table.add_column("Operator", style="yellow")
    table.add_column("Test Program", style="blue")
    table.add_column("Test Type", style="blue")
    table.add_column("Sequence", style="blue")
    table.add_column("Socket", style="white")
    table.add_column("Log File", style="white")
    table.add_column("Insert Time", style="white")
    table.add_column("Yield", style="white")
    table.add_column("UUT Type", style="white")
    rows = payload if isinstance(payload, list) else ([payload] if payload else [])
    if not rows:
        table.add_row(*(["-"] * 15))
        return table
    for entry in rows:
        if not isinstance(entry, dict):
            table.add_row(str(entry), *(["-"] * 14))
            continue
        table.add_row(
            str(entry.get("ResultSetName") or entry.get("Id", "-")),
            str(entry.get("SerialNumber", "-")),
            str(entry.get("Outcome", "-")),
            format_timestamp(entry.get("StartDateTime")),
            format_timestamp(entry.get("StopDateTime")),
            str(entry.get("StationName", "-")),
            str(entry.get("OperatorName", "-")),
            str(entry.get("TestProgramName", "-")),
            str(entry.get("TestTypeName", "-")),
            str(entry.get("SequenceName", "-")),
            str(entry.get("TestSocketIndex", "-")),
            str(entry.get("LogFileURL", "-")),
            format_timestamp(entry.get("InsertDateTime")),
            str(entry.get("Yield", "-")),
            str(entry.get("UUTTypeName", "-")),
        )
    return table


def render_serial_status(payload: Any) -> Table:
    """Render serial-based test results as a table with all columns."""
    table = Table(show_header=True, header_style="bold")
    table.add_column("Result Set", style="cyan")
    table.add_column("Serial", style="cyan")
    table.add_column("Outcome", style="green")
    table.add_column("Start", style="magenta")
    table.add_column("Stop", style="magenta")
    table.add_column("Station", style="yellow")
    table.add_column("Operator", style="yellow")
    table.add_column("Test Program", style="blue")
    table.add_column("Test Type", style="blue")
    table.add_column("Sequence", style="blue")
    table.add_column("Socket", style="white")
    table.add_column("Log File", style="white")
    table.add_column("Insert Time", style="white")
    table.add_column("Yield", style="white")
    table.add_column("UUT Type", style="white")
    rows = payload if isinstance(payload, list) else ([payload] if payload else [])
    if not rows:
        table.add_row(*(["-"] * 15))
        return table
    for entry in rows:
        if not isinstance(entry, dict):
            table.add_row(str(entry), *(["-"] * 14))
            continue
        table.add_row(
            str(entry.get("ResultSetName") or entry.get("Id", "-")),
            str(entry.get("SerialNumber", "-")),
            str(entry.get("Outcome", "-")),
            format_timestamp(entry.get("StartDateTime")),
            format_timestamp(entry.get("StopDateTime")),
            str(entry.get("StationName", "-")),
            str(entry.get("OperatorName", "-")),
            str(entry.get("TestProgramName", "-")),
            str(entry.get("TestTypeName", "-")),
            str(entry.get("SequenceName", "-")),
            str(entry.get("TestSocketIndex", "-")),
            str(entry.get("LogFileURL", "-")),
            format_timestamp(entry.get("InsertDateTime")),
            str(entry.get("Yield", "-")),
            str(entry.get("UUTTypeName", "-")),
        )
    return table


def resolve_serial(entry: dict[str, Any]) -> str:
    """Resolve the serial number from an entry."""
    value = entry.get("serialNumber") or entry.get("SerialNumber")
    return str(value) if value else "-"


def format_timestamp(value: Any) -> str:
    """Format a timestamp for display."""
    if not value:
        return "-"
    if isinstance(value, str):
        sanitized = value.replace("Z", "+00:00") if value.endswith("Z") else value
        try:
            dt = datetime.fromisoformat(sanitized)
            return dt.strftime("%Y-%m-%d %H:%M:%S")
        except ValueError:
            return value
    if isinstance(value, datetime):
        return value.strftime("%Y-%m-%d %H:%M:%S")
    return str(value)


__all__ = [
    "DEFAULT_BASE_URL",
    "DEFAULT_CONFIG_PATH",
    "API_DATE_FORMAT",
    "CLI_DATE_FORMATS",
    "EPOCH_START",
    "console",
    "OutputFormat",
    "resolve_config_path",
    "load_state_or_exit",
    "emit",
    "resolve_uut_window",
    "resolve_box_window",
    "resolve_window",
    "format_api_datetime",
    "render_uut_status",
    "render_box_list",
    "render_box_detail",
    "render_uut_table",
    "resolve_serial",
    "format_timestamp",
]
