"""Authentication commands."""

from __future__ import annotations

import logging
import socket
import time
import webbrowser
from datetime import datetime, timedelta, timezone

import httpx
import typer

from ..config import CLIConfig
from ..exceptions import CLIError, ConfigurationError
from ..keychain import (
    AuthSource,
    delete_api_key,
    get_api_key_with_env_override,
    get_env_api_key_override,
    store_api_key,
)
from ._context import require_cli_context

POLL_INTERVAL_SECONDS = 7.0  # keep below backend rate limit (10 req/min)
KEY_PREFIX_LENGTH = 14  # "ctxme_" (6 chars) + 8 chars for lookup
logger = logging.getLogger(__name__)
OVERRIDE_WARNING = "⚠️  CLI__API_KEY is set - keychain will be updated but not used"


def _http_client(config: CLIConfig, headers: dict[str, str] | None = None) -> httpx.Client:
    if not config.api_base_url:
        raise ConfigurationError("API base URL is not configured. Run `ctxme config --api <url>`.")

    return httpx.Client(
        base_url=config.api_base_url,
        timeout=10.0,
        headers=headers or {"Accept": "application/json"},
    )


def _handle_error(resp: httpx.Response) -> None:
    try:
        resp.raise_for_status()
    except httpx.HTTPStatusError as exc:  # pragma: no cover - exercised via CLI flow
        message = f"Backend returned {resp.status_code}"
        try:
            payload = resp.json()
            error_detail = payload.get("error", {}).get("message")
            if error_detail:
                message = f"{message}: {error_detail}"
        except (ValueError, TypeError, KeyError) as parse_exc:  # noqa: PERF203
            logger.debug("Failed to parse error payload", exc_info=parse_exc)
        raise CLIError(message) from exc


def _start_device_flow(client: httpx.Client, label: str | None) -> dict:
    payload = {"label": label} if label else {}
    resp = client.post("/v1/auth/device", json=payload)
    _handle_error(resp)
    return resp.json()


def _poll_device_status(
    client: httpx.Client,
    device_code: str,
    *,
    deadline: datetime,
    console,
) -> str:
    while datetime.now(tz=timezone.utc) < deadline:
        time.sleep(POLL_INTERVAL_SECONDS)
        resp = client.get(f"/v1/auth/device/{device_code}/status")
        if resp.status_code == 429:
            retry_after = int(resp.headers.get("Retry-After", "5"))
            console.print(
                f"[yellow]Rate limited while polling. Retrying in {retry_after}s...[/yellow]"
            )
            time.sleep(retry_after)
            continue
        _handle_error(resp)
        status_payload = resp.json()
        status = status_payload.get("status")
        if status == "pending":
            continue
        if status == "expired":
            raise CLIError("Device code expired. Please run `ctxme auth login` again.")
        if status == "complete":
            api_key = status_payload.get("api_key")
            if not api_key:
                raise CLIError("Device flow completed but no API key was returned. Please retry.")
            return api_key
        raise CLIError(f"Unexpected device flow status: {status_payload}")

    raise CLIError("Device flow timed out. Please run `ctxme auth login` again.")


def register(app: typer.Typer) -> None:
    auth_app = typer.Typer(help="Authentication commands (device flow or API key).")

    @auth_app.command("login", help="Authenticate via device flow and store API key in keychain.")
    def auth_login(
        ctx: typer.Context,
        open_browser: bool = typer.Option(
            True, "--open-browser/--no-open-browser", help="Open the verification URL in a browser."
        ),
    ) -> None:
        cli_ctx = require_cli_context(ctx)
        config = cli_ctx.load_config()
        console = cli_ctx.console
        env_override_active = get_env_api_key_override() is not None

        with _http_client(config) as client:
            label = f"cli-{socket.gethostname()}" if socket.gethostname() else "cli-device"
            start_payload = _start_device_flow(client, label=label)
            verification_url = start_payload["verification_url"]
            user_code = start_payload["user_code"]
            device_code = start_payload["device_code"]
            expires_in = int(start_payload.get("expires_in", 600))

            console.print(
                "[bold]To sign in:[/bold]\n"
                f"1) Visit: {verification_url}\n"
                f"2) Enter code: [dark_cyan]{user_code}[/dark_cyan]"
            )
            if open_browser:
                webbrowser.open(verification_url, new=2, autoraise=True)
                console.print("[green]Opened browser for authentication.[/green]")

            deadline = datetime.now(tz=timezone.utc) + timedelta(seconds=expires_in)
            console.print("[dodger_blue2]Waiting for authorization...[/dodger_blue2]")
            api_key = _poll_device_status(client, device_code, deadline=deadline, console=console)

        store_api_key(api_key)
        console.print("[green]Signed in and API key stored in keychain.[/green]")
        if env_override_active:
            console.print(f"[yellow]{OVERRIDE_WARNING}[/yellow]")

    @auth_app.command("logout", help="Remove stored API key from keychain.")
    def auth_logout(ctx: typer.Context) -> None:
        cli_ctx = require_cli_context(ctx)
        config = cli_ctx.load_config()
        if config.default_project is not None:
            config = config.model_copy(update={"default_project": None})
            cli_ctx.save_config(config)
        delete_api_key()
        cli_ctx.console.print("[green]Signed out. API key removed from keychain.[/green]")

    @auth_app.command("status", help="Show current authentication status.")
    def auth_status(ctx: typer.Context) -> None:
        cli_ctx = require_cli_context(ctx)
        console = cli_ctx.console
        api_key, source = get_api_key_with_env_override()
        if not api_key:
            console.print("[yellow]Not signed in.[/yellow]")
            return

        prefix = api_key[:KEY_PREFIX_LENGTH]
        source_label = "from environment" if source == AuthSource.ENV else "from keychain"
        console.print(f"[green]API key present {source_label} (prefix {prefix}).[/green]")

        config = cli_ctx.load_config()
        try:
            with _http_client(
                config, headers={"Authorization": f"Bearer {api_key}", "Accept": "application/json"}
            ) as client:
                resp = client.get("/v1/auth/keys")
                _handle_error(resp)
                keys = resp.json().get("keys", [])
                match = next((k for k in keys if k.get("key_prefix") == prefix), None)
                if match:
                    expires_at = match.get("expires_at") or "Never"
                    console.print(
                        f"Status: {match.get('status', 'unknown')}, Expires: {expires_at}, "
                        f"Scopes: {','.join(match.get('scopes', []))}"
                    )
                else:
                    console.print("Unable to match stored key to backend metadata.")
        except CLIError as exc:
            console.print(f"[yellow]Warning:[/yellow] {exc}")

    @auth_app.command("set-key", help="Store API key securely in the OS keychain.")
    def auth_set_key(
        ctx: typer.Context,
        api_key: str = typer.Argument(..., help="API key that starts with 'ctxme_'"),
    ) -> None:
        cli_ctx = require_cli_context(ctx)
        env_override_active = get_env_api_key_override() is not None
        store_api_key(api_key)
        cli_ctx.console.print("[green]API key stored in keychain.[/green]")
        if env_override_active:
            cli_ctx.console.print(f"[yellow]{OVERRIDE_WARNING}[/yellow]")

    app.add_typer(auth_app, name="auth")
