"""Device Authorization Grant endpoints for CLI authentication.

The CLI never holds client secrets.  These endpoints proxy the Auth0
Device Authorization flow server-side so the CLI only needs to display
a URL + user code and poll for completion.
"""

from __future__ import annotations

import hashlib
import logging
import uuid
from datetime import UTC, datetime, timedelta

from fastapi import APIRouter, HTTPException, Request
from fastapi.responses import JSONResponse
from pydantic import BaseModel

from .jwt import resolve_org_login, validate_access_token

logger = logging.getLogger(__name__)

device_router = APIRouter(prefix="/auth/device")


class DeviceCodeRequest(BaseModel):
    """Optional hints the CLI can send when starting device auth."""

    org: str = ""


class DeviceTokenRequest(BaseModel):
    device_code: str


@device_router.post("/code", response_class=JSONResponse)
async def request_device_code(request: Request, body: DeviceCodeRequest):
    """Start the device authorization flow by requesting a code from Auth0."""
    settings = request.app.state.settings
    client_id = settings.auth0_device_client_id or settings.auth0_client_id
    if not settings.auth0_domain or not client_id:
        raise HTTPException(status_code=503, detail="Device auth not configured")
    if not settings.auth0_audience:
        logger.error("AUTH0_AUDIENCE not configured — device auth requires an API audience")
        raise HTTPException(status_code=503, detail="Device auth not configured")

    url = f"https://{settings.auth0_domain}/oauth/device/code"
    params: dict[str, str] = {
        "client_id": client_id,
        "scope": "openid profile email offline_access",
        "audience": settings.auth0_audience,
    }

    http = request.app.state.auth0_http
    resp = await http.post(url, data=params)

    if resp.status_code != 200:
        error = (
            resp.json().get("error", "")
            if resp.headers.get("content-type", "").startswith("application/json")
            else ""
        )
        logger.warning("Auth0 device/code failed: %s error=%s", resp.status_code, error)
        raise HTTPException(status_code=502, detail="Failed to start device auth")

    data = resp.json()
    return JSONResponse(
        content={
            "device_code": data["device_code"],
            "user_code": data["user_code"],
            "verification_uri": data.get("verification_uri", ""),
            "verification_uri_complete": data.get("verification_uri_complete", ""),
            "interval": data.get("interval", 5),
            "expires_in": data.get("expires_in", 900),
        }
    )


@device_router.post("/token", response_class=JSONResponse)
async def poll_device_token(request: Request, body: DeviceTokenRequest):
    """Poll Auth0 for the device token.  Returns status + tokens on approval."""
    settings = request.app.state.settings
    client_id = settings.auth0_device_client_id or settings.auth0_client_id
    if not settings.auth0_domain or not client_id:
        raise HTTPException(status_code=503, detail="Device auth not configured")

    url = f"https://{settings.auth0_domain}/oauth/token"
    params = {
        "grant_type": "urn:ietf:params:oauth:grant-type:device_code",
        "client_id": client_id,
        "device_code": body.device_code,
    }

    http = request.app.state.auth0_http
    resp = await http.post(url, data=params)

    data = resp.json()

    # Auth0 returns 403 while the user hasn't authorized yet
    if resp.status_code == 403:
        error = data.get("error", "")
        if error == "authorization_pending":
            return JSONResponse(content={"status": "pending"})
        if error == "slow_down":
            return JSONResponse(content={"status": "slow_down"})
        if error == "expired_token":
            return JSONResponse(content={"status": "expired"})
        if error == "access_denied":
            return JSONResponse(content={"status": "denied"})
        return JSONResponse(content={"status": "error", "detail": error})

    if resp.status_code != 200:
        logger.warning(
            "Auth0 device/token failed: %s error=%s", resp.status_code, data.get("error", "")
        )
        return JSONResponse(
            content={"status": "error", "detail": "Token exchange failed"},
            status_code=502,
        )

    # Token approved — validate, upsert user, create session
    access_token = data.get("access_token", "")
    refresh_token = data.get("refresh_token", "")
    expires_in = data.get("expires_in", 86400)

    # Validate access token and extract claims
    try:
        claims = await validate_access_token(access_token, settings)
    except ValueError as exc:
        # Missing configuration (e.g. AUTH0_AUDIENCE)
        logger.error("Device auth: server misconfiguration: %s", exc)
        raise HTTPException(status_code=503, detail="Auth service misconfigured") from exc
    except Exception as exc:
        logger.warning("Device auth: access token validation failed", exc_info=True)
        raise HTTPException(status_code=500, detail="Access token validation failed") from exc

    sub = claims.get("sub", "")
    email = claims.get("email", "") or claims.get("https://specwright.dev/email", "")
    name = claims.get("name", "") or claims.get("https://specwright.dev/name", "")

    # Resolve org
    registry = getattr(request.app.state, "registry", None)
    org_login = await resolve_org_login(claims, registry)

    # Upsert user
    user_store = getattr(request.app.state, "user_store", None)
    user_record = None
    if user_store and sub:
        try:
            user_record = await user_store.upsert_user(
                auth0_sub=sub,
                email=email,
                name=name,
            )
        except Exception:
            logger.warning("Device auth: failed to upsert user", exc_info=True)

    # Create session
    session_created = False
    session_store = getattr(request.app.state, "session_store", None)
    if session_store and user_record and refresh_token:
        refresh_hash = hashlib.sha256(refresh_token.encode()).hexdigest()
        session_id = str(uuid.uuid4())
        try:
            await session_store.create_session(
                session_id=session_id,
                user_id=user_record["id"],
                org_login=org_login,
                device_label="cli",
                refresh_hash=refresh_hash,
                expires_at=datetime.now(UTC) + timedelta(days=30),
            )
            session_created = True
        except Exception:
            logger.warning("Device auth: failed to create session", exc_info=True)

    return JSONResponse(
        content={
            "status": "approved",
            "access_token": access_token,
            "refresh_token": refresh_token,
            "expires_in": expires_in,
            "email": email,
            "org": org_login,
            "session_created": session_created,
        }
    )
