"""CLI auth commands — login, logout, and status.

Provides the ``sika auth`` command group for managing authentication
credentials via OAuth2 PKCE browser flow.
"""

from __future__ import annotations

import socket
import sys
import webbrowser
from typing import Any

import typer

from sika._mask import mask_api_key
from sika.auth import CredentialFile
from sika_cli.auth import (
    AuthFlowError,
    CallbackServer,
    build_authorize_url,
    create_api_token,
    exchange_code_for_tokens,
    extract_email_from_id_token,
    generate_pkce_params,
    get_cognito_config,
)
from sika_cli.errors import handle_errors
from sika_cli.output import get_console, get_error_console, print_json

auth_app = typer.Typer(name="auth", help="Manage authentication credentials.")

# ---------------------------------------------------------------------------
# sika auth login
# ---------------------------------------------------------------------------

_NON_TTY_MESSAGE = (
    "Cannot authenticate in non-interactive mode.\n"
    "Use SIKA_API_KEY environment variable or --token-stdin."
)


@auth_app.command()
@handle_errors
def login(ctx: typer.Context) -> None:
    """Authenticate via browser (OAuth2 PKCE).

    Opens the Sika login page in your default browser.  After
    authenticating, a long-lived API token is created and stored
    in ``~/.sika/credentials``.

    Requires an interactive terminal.  In CI/CD or scripts, use
    ``SIKA_API_KEY`` or ``--token-stdin`` instead.
    """
    obj: dict[str, Any] = ctx.ensure_object(dict)
    json_mode = obj.get("json", False)
    console = get_console(ctx)
    err_console = get_error_console(ctx)

    # Non-TTY detection: refuse immediately in non-interactive mode
    if not sys.stdin.isatty():
        if json_mode:
            print_json({"error": _NON_TTY_MESSAGE})
        else:
            err_console.print(_NON_TTY_MESSAGE, style="red")
        raise typer.Exit(code=2) from None

    environment = obj.get("environment", "production")
    profile = obj.get("profile") or "default"
    config = get_cognito_config(environment)

    # 1. Generate PKCE parameters
    pkce = generate_pkce_params()

    # 2. Start local callback server
    server = CallbackServer()
    server.start()

    try:
        # 3. Build authorization URL and open browser
        auth_url = build_authorize_url(
            cognito_domain=config["domain"],
            client_id=config["client_id"],
            redirect_uri=server.redirect_uri,
            pkce=pkce,
        )

        console.print("Opening browser for authentication...")
        if not webbrowser.open(auth_url):
            err_console.print(
                "Could not open browser. Visit this URL manually:",
                style="yellow",
            )
            console.print(auth_url)

        # 4. Wait for callback
        console.print("Waiting for authentication callback...", style="dim")
        if not server.wait(timeout=300):
            if json_mode:
                print_json({"error": "Authentication timed out."})
            else:
                err_console.print("Authentication timed out.", style="red")
            raise typer.Exit(code=2) from None

        result = server.result

        # Check for OAuth errors
        if result.error:
            msg = f"Authentication failed: {result.error}"
            if json_mode:
                print_json({"error": msg})
            else:
                err_console.print(msg, style="red")
            raise typer.Exit(code=2) from None

        # Validate state (CSRF protection)
        if result.state != pkce.state:
            msg = "Authentication failed: state mismatch (possible CSRF)."
            if json_mode:
                print_json({"error": msg})
            else:
                err_console.print(msg, style="red")
            raise typer.Exit(code=2) from None

        if not result.code:
            msg = "Authentication failed: no authorization code received."
            if json_mode:
                print_json({"error": msg})
            else:
                err_console.print(msg, style="red")
            raise typer.Exit(code=2) from None

        # 5. Exchange authorization code for tokens
        try:
            tokens = exchange_code_for_tokens(
                cognito_domain=config["domain"],
                client_id=config["client_id"],
                redirect_uri=server.redirect_uri,
                code=result.code,
                code_verifier=pkce.code_verifier,
            )

            # 6. Extract email from ID token
            email = extract_email_from_id_token(tokens.id_token) or "unknown"

            # 7. Create API token via platform endpoint
            hostname = socket.gethostname()
            api_token = create_api_token(
                platform_api_url=config["platform_api_url"],
                id_token=tokens.id_token,
                label=f"sika-cli on {hostname}",
            )
        except AuthFlowError as exc:
            msg = str(exc)
            if json_mode:
                print_json({"error": msg})
            else:
                err_console.print(msg, style="red")
            raise typer.Exit(code=2) from None

        # 8. Store API token and email
        cred_file = CredentialFile()
        cred_file.write_api_key(api_token, profile=profile)
        if email != "unknown":
            cred_file.write_email(email, profile=profile)

        # 9. Print success
        if json_mode:
            print_json({
                "authenticated": True,
                "email": email,
                "profile": profile,
                "environment": environment,
            })
        else:
            console.print(f"Authenticated as [bold]{email}[/bold].")
            console.print(f"Credentials saved to {cred_file.path}")

    finally:
        server.stop()


# ---------------------------------------------------------------------------
# sika auth logout
# ---------------------------------------------------------------------------


@auth_app.command()
@handle_errors
def logout(ctx: typer.Context) -> None:
    """Remove stored credentials.

    Deletes the API key for the current profile from the credential
    file (``~/.sika/credentials``).
    """
    obj: dict[str, Any] = ctx.ensure_object(dict)
    json_mode = obj.get("json", False)
    console = get_console(ctx)
    profile = obj.get("profile") or "default"

    cred_file = CredentialFile()
    cred_file.delete_api_key(profile=profile)

    if json_mode:
        print_json({"logged_out": True, "profile": profile})
    else:
        console.print(f"Logged out (profile: {profile}).")


# ---------------------------------------------------------------------------
# sika auth status
# ---------------------------------------------------------------------------


@auth_app.command()
@handle_errors
def status(ctx: typer.Context) -> None:
    """Show authentication status.

    Checks whether credentials are configured for the current profile
    and displays the masked API key, profile, and environment.
    """
    obj: dict[str, Any] = ctx.ensure_object(dict)
    json_mode = obj.get("json", False)
    console = get_console(ctx)
    profile = obj.get("profile") or "default"
    environment = obj.get("environment", "production")

    cred_file = CredentialFile()
    api_key = cred_file.read_api_key(profile=profile)

    authenticated = api_key is not None

    if json_mode:
        data: dict[str, Any] = {
            "authenticated": authenticated,
            "profile": profile,
            "environment": environment,
        }
        if authenticated:
            data["api_key"] = mask_api_key(api_key)  # type: ignore[arg-type]
        print_json(data)
        return

    if not authenticated:
        console.print("Not authenticated.", style="yellow")
        console.print(f"Profile:     {profile}")
        console.print(f"Environment: {environment}")
        console.print(
            "\nRun [bold]sika auth login[/bold] or set "
            "[bold]SIKA_API_KEY[/bold] to authenticate."
        )
        return

    console.print("Authenticated.", style="green")
    console.print(f"API key:     {mask_api_key(api_key)}")  # type: ignore[arg-type]
    console.print(f"Profile:     {profile}")
    console.print(f"Environment: {environment}")
