"""Typer entry point for the Context Platform CLI."""

from __future__ import annotations

import os
import sys
from importlib import metadata
from io import StringIO

import click
import httpx
import typer
from rich.console import Console

from shared import (
    AuthenticationError,
    BackendRequestError,
    ConfigError,
    NotFoundError,
    SharedError,
    ValidationFailure,
)

from .clients import ClientFactory
from .commands import auth, config, items, projects
from .config import ConfigStore
from .context import CLIContext
from .exceptions import CLIError
from .styles import apply_help_styles

# Apply before app creation so Typer picks up updated Rich help styles.
apply_help_styles()

app = typer.Typer(help="Interact with the Context Platform.", no_args_is_help=True)
console = Console()
_context = CLIContext(console=console, config_store=ConfigStore(), client_factory=ClientFactory())


def _get_version() -> str:
    """Return the installed CLI package version or dev fallback."""
    try:
        return metadata.version("ctxme")
    except metadata.PackageNotFoundError:
        return "dev"


def _version_callback(value: bool) -> None:
    if value:
        console.print(f"ctxme {_get_version()}")
        raise typer.Exit()


def _is_debug() -> bool:
    """Return True when debug output should be printed."""
    value = os.getenv("CTXME_DEBUG", "").strip().lower()
    return value in {"1", "true", "yes"}


def _shared_error_message(exc: SharedError) -> str:
    """Return a user-friendly message for shared exceptions."""
    if isinstance(exc, AuthenticationError):
        return "Authentication failed. Run 'ctxme auth login'."
    if isinstance(exc, NotFoundError):
        return f"Item/Project not found: {exc}"
    if isinstance(exc, ValidationFailure):
        return f"Validation error: {exc}"
    if isinstance(exc, BackendRequestError):
        return f"API error: {exc}"
    if isinstance(exc, ConfigError):
        return f"Configuration error: {exc}"
    return str(exc)


def _http_error_message(exc: httpx.HTTPError) -> str:
    """Return a user-friendly message for HTTP exceptions."""
    if isinstance(exc, httpx.ConnectError):
        return "Unable to connect to API server. Check your network connection and API URL."
    if isinstance(exc, httpx.TimeoutException):
        return "Request timed out. Try again."
    if isinstance(exc, httpx.HTTPStatusError):
        status = exc.response.status_code if exc.response else "unknown"
        return f"Server error ({status}). Try again later."
    return "Network error. Try again."


def _print_shared_debug(exc: SharedError, *, console: Console) -> None:
    """Print extra request details for shared errors when available."""
    request_url = getattr(exc, "request_url", None)
    status_code = getattr(exc, "status_code", None)
    request_id = getattr(exc, "request_id", None)
    response_body = getattr(exc, "response_body", None)
    if request_url:
        console.print(f"[yellow]Request URL:[/yellow] {request_url}")
    if status_code is not None:
        console.print(f"[yellow]Status:[/yellow] {status_code}")
    if request_id:
        console.print(f"[yellow]Request ID:[/yellow] {request_id}")
    if response_body:
        console.print("[yellow]Response body:[/yellow]")
        console.print(response_body)


def _print_http_debug(exc: httpx.HTTPError, *, console: Console) -> None:
    """Print HTTP request/response details for debugging."""
    request = getattr(exc, "request", None)
    response = getattr(exc, "response", None)
    if request:
        console.print(f"[yellow]Request:[/yellow] {request.method} {request.url}")
    if response:
        console.print(f"[yellow]Status:[/yellow] {response.status_code}")
        request_id = response.headers.get("x-request-id")
        if request_id:
            console.print(f"[yellow]Request ID:[/yellow] {request_id}")
        if response.text:
            console.print("[yellow]Response body:[/yellow]")
            console.print(response.text)


@app.callback(invoke_without_command=True)
def _inject_context(
    ctx: typer.Context,
    version: bool = typer.Option(
        False,
        "--version",
        is_eager=True,
        callback=_version_callback,
        help="Show CLI version and exit.",
    ),
    debug: bool = typer.Option(False, "--debug", hidden=True),
) -> None:
    """Attach shared context to Typer's state."""
    _ = version
    if debug:
        os.environ["CTXME_DEBUG"] = "1"
    ctx.obj = _context


def _register_commands() -> None:
    config.register(app)
    auth.register(app)
    items.register(app)
    projects.register(app)


_register_commands()


def main() -> None:
    """Console script entry point."""
    err_console = Console(stderr=True)
    try:
        rv = app(prog_name="ctxme", standalone_mode=False)
        # With standalone_mode=False, typer.Exit returns exit code instead of raising
        if isinstance(rv, int):
            raise SystemExit(rv)
    except (KeyboardInterrupt, SystemExit):
        raise
    except typer.Exit as exc:
        raise SystemExit(exc.exit_code)
    except click.Abort:
        click.echo("Aborted!", err=True)
        raise SystemExit(1)
    except click.UsageError as exc:
        # Show original error with styling
        exc.show()
        # If "Missing command.", append help text to guide the user
        if exc.message == "Missing command." and exc.ctx is not None:
            # Typer's get_help() prints to stdout as a side effect.
            # Capture it and redirect to stderr for consistency.
            old_stdout = sys.stdout
            sys.stdout = captured = StringIO()
            try:
                exc.ctx.get_help()
            finally:
                sys.stdout = old_stdout
            help_output = captured.getvalue()
            if help_output:
                err_console.print()  # blank line separator
                # Print raw captured output to stderr (already formatted by Typer)
                sys.stderr.write(help_output)
        raise SystemExit(exc.exit_code)
    except click.ClickException as exc:
        exc.show()
        raise SystemExit(exc.exit_code)
    except CLIError as exc:
        err_console.print(f"[red]Error:[/red] {exc}")
        if _is_debug():
            err_console.print_exception()
        raise SystemExit(1)
    except SharedError as exc:
        err_console.print(f"[red]Error:[/red] {_shared_error_message(exc)}")
        if _is_debug():
            _print_shared_debug(exc, console=err_console)
            err_console.print_exception()
        raise SystemExit(1)
    except httpx.HTTPError as exc:
        err_console.print(f"[red]Error:[/red] {_http_error_message(exc)}")
        if _is_debug():
            _print_http_debug(exc, console=err_console)
            err_console.print_exception()
        raise SystemExit(1)
    except Exception:
        err_console.print("[red]Error:[/red] An unexpected error occurred.")
        if _is_debug():
            err_console.print_exception()
        else:
            err_console.print("Run with CTXME_DEBUG=1 for details.")
        raise SystemExit(1)


if __name__ == "__main__":
    main()
