"""Auth routes: login, callback, logout."""

from __future__ import annotations

import logging

from fastapi import APIRouter, Request
from fastapi.responses import RedirectResponse

from specwright import analytics

from .jwt import validate_access_token
from .oauth import oauth

logger = logging.getLogger(__name__)

auth_router = APIRouter(prefix="/auth")

_ALLOWED_CONNECTIONS: frozenset[str] = frozenset({"github", "google-oauth2", "email"})


@auth_router.get("/login")
async def login(request: Request, org: str = "", connection: str = ""):
    """Redirect to Auth0 Universal Login.

    If ``?org=<slug>`` is provided and Auth0 Organizations mode is enabled,
    the user authenticates into that specific org so the returned token
    contains ``org_id`` and ``permissions`` claims.
    """
    settings = request.app.state.settings
    if not settings.auth0_enabled:
        return RedirectResponse(url="/")

    redirect_uri = str(request.url_for("auth_callback"))
    kwargs: dict = {}

    # Store the requested org as a redirect hint (always, regardless of orgs mode)
    if org:
        request.session["login_org"] = org

    if org and settings.auth0_orgs_enabled:
        # Resolve org slug → Auth0 org_id via the installation registry
        registry = getattr(request.app.state, "registry", None)
        auth0_org_id = ""
        if registry:
            try:
                installation = await registry.get_installation_by_org(org)
                if installation and installation.auth0_org_id:
                    auth0_org_id = installation.auth0_org_id
            except Exception:
                logger.debug("Failed to resolve org %s to Auth0 org_id", org, exc_info=True)

        if auth0_org_id:
            kwargs["organization"] = auth0_org_id

    if connection and connection in _ALLOWED_CONNECTIONS:
        kwargs["connection"] = connection

    return await oauth.auth0.authorize_redirect(request, redirect_uri, **kwargs)


@auth_router.get("/callback", name="auth_callback")
async def callback(request: Request):
    """Handle Auth0 callback — exchange code for tokens, set session."""
    settings = request.app.state.settings
    if not settings.auth0_enabled:
        return RedirectResponse(url="/")

    # Auth0 may return an error instead of an authorization code
    error = request.query_params.get("error")
    if error:
        error_desc = request.query_params.get("error_description", "")
        logger.warning("Auth0 callback error: %s — %s", error, error_desc)
        return RedirectResponse(url="/auth/login")

    token = await oauth.auth0.authorize_access_token(request)
    userinfo = token.get("userinfo", {})

    settings = request.app.state.settings
    org_id = ""
    org_login = ""
    permissions: list[str] = []

    # Extract permissions (and optionally org) from the access token.
    # RBAC permissions require auth0_audience to be set so Auth0 returns a
    # JWT access token instead of an opaque one.  Organizations (org_id) are
    # an independent Auth0 feature — only extracted when present in claims.
    if settings.auth0_audience:
        access_token_claims: dict = {}
        raw_access_token = token.get("access_token", "")
        if raw_access_token:
            try:
                access_token_claims = await validate_access_token(raw_access_token, settings)
            except Exception as exc:
                logger.warning(
                    "Access token verification failed — permissions not extracted",
                    exc_info=True,
                )
                analytics.capture_exception(exc, properties={"context": "auth_callback"})

        permissions = access_token_claims.get("permissions", [])

        # Org extraction — only present when Auth0 Organizations are used
        org_id = access_token_claims.get("org_id", "")
        if org_id:
            registry = getattr(request.app.state, "registry", None)
            if registry:
                try:
                    installation = await registry.get_installation_by_auth0_org(org_id)
                    if installation:
                        org_login = installation.org_login
                except Exception:
                    logger.debug("Failed to resolve org_id %s", org_id, exc_info=True)

    # Fallback: use login_org from session if we couldn't resolve from token
    if not org_login:
        org_login = request.session.pop("login_org", "")

    # Upsert user in DB (best-effort)
    is_new_user = False
    user_store = getattr(request.app.state, "user_store", None)
    if user_store and userinfo.get("sub"):
        try:
            user_record = await user_store.upsert_user(
                auth0_sub=userinfo["sub"],
                email=userinfo.get("email", ""),
                name=userinfo.get("name", ""),
                picture=userinfo.get("picture", ""),
            )
            is_new_user = user_record.get("is_new", False)
        except Exception:
            logger.debug("Failed to upsert user", exc_info=True)

    request.session["user"] = {
        "sub": userinfo.get("sub", ""),
        "email": userinfo.get("email", ""),
        "name": userinfo.get("name", ""),
        "picture": userinfo.get("picture", ""),
        "org_id": org_id,
        "org_login": org_login,
        "permissions": permissions,
    }

    # If Auth0 passed the GitHub identity via custom claim, populate the
    # github_user session so the editor works immediately (no second OAuth).
    github_claim = userinfo.get("https://specwright.dev/github")
    if isinstance(github_claim, dict) and github_claim.get("token"):
        request.session["github_user"] = {
            "login": github_claim.get("login", ""),
            "name": github_claim.get("name", ""),
            "email": userinfo.get("email", ""),
            "avatar_url": github_claim.get("avatar_url", ""),
            "token": github_claim["token"],
        }

    # Use Auth0 sub (opaque, stable) as distinct_id to avoid sending PII to PostHog.
    # identify() links the sub to email/name for display in the PostHog UI.
    auth0_sub = userinfo.get("sub", "")
    if auth0_sub:
        analytics.identify(
            auth0_sub,
            {
                "email": userinfo.get("email", ""),
                "name": userinfo.get("name", ""),
                "org": org_login,
            },
        )
    analytics.track(
        "user_logged_in",
        distinct_id=auth0_sub or analytics.SERVER_ACTOR,
        properties={"is_new_user": is_new_user, "org": org_login},
        groups={"organization": org_login} if org_login else None,
    )

    # Redirect to the org dashboard if we know which org
    if org_login:
        if is_new_user:
            return RedirectResponse(url=f"/app/{org_login}/welcome")
        return RedirectResponse(url=f"/app/{org_login}/")
    return RedirectResponse(url="/app")


@auth_router.get("/logout")
async def logout(request: Request):
    """Clear session and redirect to Auth0 logout."""
    request.session.pop("user", None)
    request.session.pop("github_user", None)
    settings = request.app.state.settings
    return_url = str(request.base_url)
    return RedirectResponse(
        f"https://{settings.auth0_domain}/v2/logout"
        f"?client_id={settings.auth0_client_id}&returnTo={return_url}"
    )
