"""Typer-powered CLI entry point for lr-qrm."""

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 . import __version__
from .client import LoginState, QrmClient, QrmClientError
from .config import load_login_state

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


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


app = typer.Typer(help="Interact with the Qestit QRM service.")
console = Console()
uut_app = typer.Typer(help="Inspect Units Under Test (UUTs).")
box_app = typer.Typer(help="Inspect production boxes.")
app.add_typer(uut_app, name="uut")
app.add_typer(box_app, name="box")

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


@app.callback(invoke_without_command=True)
def main(
    ctx: typer.Context,
    version: bool = typer.Option(
        False,
        "--version",
        help="Display the lr-qrm version and exit.",
        rich_help_panel="Global Options",
    ),
) -> None:
    """Top-level callback showing help or version when requested."""

    if version:
        typer.echo(__version__)
        raise typer.Exit()
    if ctx.invoked_subcommand is None:
        typer.echo(ctx.get_help())
        raise typer.Exit()


@app.command()
def status(
    config_path: Path = typer.Option(
        None,
        "--config-path",
        help="Override the location of the login.json file.",
    ),
    base_url: str = typer.Option(
        None,
        "--base-url",
        help="Override the base URL stored in the config (useful for testing).",
    ),
    insecure: bool = typer.Option(
        False,
        "--insecure",
        help="Disable TLS verification when contacting QRM.",
    ),
    output: OutputFormat = typer.Option(
        OutputFormat.rich,
        "--output",
        "-o",
        case_sensitive=False,
        help="Choose output format: rich (default) or json.",
    ),
) -> None:
    """Check QRM health via the `/Result/Status` endpoint."""

    state = _load_state_or_exit(config_path)

    client = QrmClient(
        base_url=base_url or str(state.base_url),
        verify_tls=not insecure and state.verify_tls,
    )

    try:
        payload = client.status(token=state.token)
    except QrmClientError as exc:
        typer.secho(f"Status check failed: {exc}", fg=typer.colors.RED)
        raise typer.Exit(code=1) from exc

    _emit(payload, output)


@app.command()
def login(
    username: str = typer.Option(..., "--username", "-u", prompt=True, help="QRM username."),
    password: str = typer.Option(
        ..., "--password", "-p", prompt=True, hide_input=True, help="QRM password."
    ),
    base_url: str = typer.Option(
        DEFAULT_BASE_URL,
        "--base-url",
        "-b",
        help="Base URL for the QRM API.",
    ),
    config_path: Path = typer.Option(
        None,
        "--config-path",
        help="Override the location of the login.json file.",
    ),
    insecure: bool = typer.Option(
        False,
        "--insecure",
        help="Disable TLS verification when contacting QRM.",
    ),
    output: OutputFormat = typer.Option(
        OutputFormat.rich,
        "--output",
        "-o",
        case_sensitive=False,
        help="Choose output format: rich (default) or json.",
    ),
) -> None:
    """Authenticate against QRM and store the bearer token locally."""

    destination = _resolve_config_path(config_path)
    destination.parent.mkdir(parents=True, exist_ok=True)

    client = QrmClient(base_url=base_url, verify_tls=not insecure)
    try:
        token = client.login(username=username, password=password)
    except QrmClientError as exc:
        typer.secho(f"Login failed: {exc}", fg=typer.colors.RED)
        raise typer.Exit(code=1) from exc

    state = LoginState(
        token=token,
        username=username,
        base_url=base_url,
        verify_tls=not insecure,
    )
    destination.write_text(state.model_dump_json(indent=2), encoding="utf-8")

    message = f"[green]Stored login token for {username} at {destination}[/green]"
    payload = {
        "username": username,
        "config_path": str(destination),
    }
    _emit(payload if output is OutputFormat.json else message, output)


@uut_app.command("status")
def uut_status(
    serial_number: str = typer.Argument(..., help="Serial number to query."),
    start: datetime | None = typer.Option(
        None,
        "--start",
        formats=CLI_DATE_FORMATS,
        help=(
            "Filter from this timestamp (UTC). Accepts ISO-8601 (e.g. 2020-01-01T00:00:00Z) "
            "or YYYY-MM-DD HH:MM:SS. Default: 1970-01-01T00:00:00Z."
        ),
    ),
    stop: datetime | None = typer.Option(
        None,
        "--stop",
        formats=CLI_DATE_FORMATS,
        help=(
            "Filter until this timestamp (UTC). Accepts ISO-8601 (e.g. 2026-02-06T23:59:00Z) "
            "or YYYY-MM-DD HH:MM:SS. Default: QRM current time."
        ),
    ),
    max_results: int = typer.Option(
        1000,
        "--max-results",
        "-m",
        min=1,
        max=5000,
        help="Maximum number of rows to return (default 1000).",
    ),
    config_path: Path = typer.Option(
        None,
        "--config-path",
        help="Override the location of the login.json file.",
    ),
    base_url: str = typer.Option(
        None,
        "--base-url",
        help="Override the base URL stored in the config (useful for testing).",
    ),
    insecure: bool = typer.Option(
        False,
        "--insecure",
        help="Disable TLS verification when contacting QRM.",
    ),
    output: OutputFormat = typer.Option(
        OutputFormat.rich,
        "--output",
        "-o",
        case_sensitive=False,
        help="Choose output format: rich (default) or json.",
    ),
) -> None:
    """Fetch UUT execution history using the `/Uut/Status` endpoint."""

    state = _load_state_or_exit(config_path)
    resolved_start, resolved_stop = _resolve_uut_window(start, stop)

    client = QrmClient(
        base_url=base_url or str(state.base_url),
        verify_tls=not insecure and state.verify_tls,
    )

    try:
        payload = client.uut_status(
            token=state.token,
            serial_number=serial_number,
            start_datetime=_format_api_datetime(resolved_start),
            stop_datetime=_format_api_datetime(resolved_stop) if resolved_stop is not None else None,
            max_results=max_results,
        )
    except QrmClientError as exc:
        typer.secho(f"UUT status check failed: {exc}", fg=typer.colors.RED)
        raise typer.Exit(code=1) from exc

    if output is OutputFormat.json:
        _emit(payload, output)
        return

    console.print(_render_uut_status(payload))


@box_app.command("list")
def box_list(
    start: datetime | None = typer.Option(
        None,
        "--start",
        formats=CLI_DATE_FORMATS,
        help=(
            "Filter from this timestamp (UTC). Accepts ISO-8601 (e.g. 2020-01-01T00:00:00Z) "
            "or YYYY-MM-DD HH:MM:SS. Default: 1970-01-01T00:00:00Z."
        ),
    ),
    stop: datetime | None = typer.Option(
        None,
        "--stop",
        formats=CLI_DATE_FORMATS,
        help=(
            "Filter until this timestamp (UTC). Accepts ISO-8601 (e.g. 2026-02-06T23:59:00Z) "
            "or YYYY-MM-DD HH:MM:SS. Default: QRM current time."
        ),
    ),
    config_path: Path = typer.Option(
        None,
        "--config-path",
        help="Override the location of the login.json file.",
    ),
    base_url: str = typer.Option(
        None,
        "--base-url",
        help="Override the base URL stored in the config (useful for testing).",
    ),
    insecure: bool = typer.Option(
        False,
        "--insecure",
        help="Disable TLS verification when contacting QRM.",
    ),
    output: OutputFormat = typer.Option(
        OutputFormat.rich,
        "--output",
        "-o",
        case_sensitive=False,
        help="Choose output format: rich (default) or json.",
    ),
) -> None:
    """List production boxes via the `/Box/List` endpoint."""

    state = _load_state_or_exit(config_path)
    resolved_start, resolved_stop = _resolve_box_window(start, stop)

    client = QrmClient(
        base_url=base_url or str(state.base_url),
        verify_tls=not insecure and state.verify_tls,
    )

    try:
        payload = client.box_list(
            token=state.token,
            start_datetime=_format_api_datetime(resolved_start),
            stop_datetime=_format_api_datetime(resolved_stop) if resolved_stop is not None else None,
        )
    except QrmClientError as exc:
        typer.secho(f"Box list failed: {exc}", fg=typer.colors.RED)
        raise typer.Exit(code=1) from exc

    if output is OutputFormat.json:
        _emit(payload, output)
        return

    console.print(_render_box_list(payload))


def _resolve_config_path(config_path: Path | None) -> Path:
    return config_path.expanduser() if config_path else DEFAULT_CONFIG_PATH


def _load_state_or_exit(config_path: Path | None) -> LoginState:
    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:
    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]:
    return _resolve_window(start, stop, default_start=EPOCH_START)


def _resolve_box_window(
    start: datetime | None,
    stop: datetime | None,
) -> tuple[datetime, datetime]:
    return _resolve_window(start, stop, default_start=EPOCH_START)


def _resolve_window(
    start: datetime | None,
    stop: datetime | None,
    *,
    default_start: datetime,
) -> tuple[datetime, datetime | None]:
    stop_value = 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:
    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:
    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:
    table = Table(show_header=True, header_style="bold")
    table.add_column("Box", style="cyan")
    table.add_column("Name", style="cyan")
    table.add_column("Status", style="green")
    table.add_column("Start", style="magenta")
    table.add_column("Stop", 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("BoxId") or entry.get("Id", "-")),
            str(entry.get("BoxName") or entry.get("Name", "-")),
            str(entry.get("Status") or entry.get("Outcome", "-")),
            _format_timestamp(entry.get("StartDateTime")),
            _format_timestamp(entry.get("StopDateTime")),
            str(entry.get("Location") or entry.get("StationName") or "-"),
        )

    return table


def _resolve_serial(entry: dict[str, Any]) -> str:
    value = entry.get("serialNumber") or entry.get("SerialNumber")
    return str(value) if value else "-"


def _format_timestamp(value: Any) -> str:
    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__ = ["app"]
