"""Auth endpoints for gnexus-auth OAuth integration."""

import asyncio
from datetime import datetime, timezone

import structlog
from typing import Annotated

from fastapi import APIRouter, Depends, HTTPException, Request, Response
from gnexus_gauth.exceptions import (
    PkceException,
    StateValidationException,
    TokenExchangeException,
)

from navi.auth.client import get_gauth_client
from navi.auth.deps import get_current_user, require_user
from navi.auth.encrypt import get_encryptor
from navi.auth import User
from navi.config import settings

log = structlog.get_logger()
router = APIRouter(prefix="/auth", tags=["auth"])

# Tracks extra per-state metadata (platform, return_to) that gnexus-auth
# does not store — keyed by the OAuth state param and cleaned up on callback.
_mobile_auth_states: dict[str, dict] = {}


def _get_redirect_uri() -> str:
    """Return the configured redirect_uri."""
    # Always use the configured redirect_uri so reverse proxies are handled
    # correctly (request.base_url would return the internal address).
    return settings.gnauth_redirect_uri


def _auth_configured() -> bool:
    return bool(settings.gnauth_client_id and settings.gnauth_client_secret)


def _sanitize_return_to(return_to: str) -> str:
    """Prevent open-redirect by allowing only relative paths."""
    if not return_to or not return_to.startswith("/"):
        return "/"
    if "://" in return_to:
        return "/"
    return return_to


@router.get("/login")
async def auth_login(
    request: Request,
    return_to: str = "/",
    platform: str | None = None,
) -> Response:
    """Redirect to gnexus-auth OAuth authorization endpoint."""
    if not _auth_configured():
        raise HTTPException(status_code=503, detail="OAuth is not configured. Set GNAUTH_CLIENT_ID and GNAUTH_CLIENT_SECRET in .env")

    redirect_uri = _get_redirect_uri()
    client = get_gauth_client(redirect_uri=redirect_uri)

    safe_return_to = _sanitize_return_to(return_to)
    auth_request = client.build_authorization_request(
        return_to=safe_return_to,
        scopes=["openid", "email", "profile", "roles", "permissions"],
    )

    # Remember platform so callback knows whether to use cookie or bridge page.
    _mobile_auth_states[auth_request.state] = {
        "platform": platform,
        "return_to": safe_return_to,
    }

    log.info(
        "auth.login_redirect",
        state=auth_request.state[:8] + "...",
        redirect_uri=redirect_uri,
        platform=platform,
        return_to=safe_return_to,
    )
    return Response(status_code=302, headers={"Location": auth_request.authorization_url})


@router.get("/callback")
async def auth_callback(code: str, state: str, request: Request) -> Response:
    """Handle OAuth callback from gnexus-auth."""
    if not _auth_configured():
        raise HTTPException(status_code=503, detail="OAuth is not configured. Set GNAUTH_CLIENT_ID and GNAUTH_CLIENT_SECRET in .env")

    redirect_uri = _get_redirect_uri()
    client = get_gauth_client(redirect_uri=redirect_uri)
    encryptor = get_encryptor()

    # Exchange code for tokens (sync IO wrapped in thread)
    try:
        token_set = await asyncio.to_thread(client.exchange_authorization_code, code, state)
    except (StateValidationException, PkceException) as e:
        log.warning("auth.invalid_state", state=state[:8], error=str(e))
        raise HTTPException(status_code=400, detail="Invalid or expired state") from e
    except TokenExchangeException as e:
        log.warning("auth.token_exchange_failed", error=str(e))
        raise HTTPException(status_code=400, detail="Token exchange failed") from e

    # Fetch user info (sync IO wrapped in thread)
    try:
        auth_user = await asyncio.to_thread(client.fetch_user, token_set.access_token)
    except Exception as e:
        log.warning("auth.fetch_user_failed", error=str(e))
        raise HTTPException(status_code=400, detail="Failed to fetch user info") from e

    # Determine role
    role = "user"
    permissions: list[str] = []
    for access in auth_user.client_access_list:
        if access.client_id == settings.gnauth_client_id:
            if settings.gnauth_admin_role_slug in (access.role_ids or []):
                role = "admin"
            permissions = list(access.permission_ids or [])
            break

    # Upsert navi_user
    try:
        from navi.api.deps import get_session_store
        store = get_session_store()
        pool = await store._get_pool()
        import json
        profile = auth_user.profile
        async with pool.acquire() as conn:
            await conn.execute(
                """INSERT INTO navi_users (
                       id, email, display_name, username, first_name, last_name,
                       phone, birth_date, country, city, locale,
                       role, permissions, created_at, updated_at
                   )
                   VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $14)
                   ON CONFLICT (id) DO UPDATE
                   SET email = EXCLUDED.email,
                       display_name = EXCLUDED.display_name,
                       username = EXCLUDED.username,
                       first_name = EXCLUDED.first_name,
                       last_name = EXCLUDED.last_name,
                       phone = EXCLUDED.phone,
                       birth_date = EXCLUDED.birth_date,
                       country = EXCLUDED.country,
                       city = EXCLUDED.city,
                       locale = EXCLUDED.locale,
                       role = EXCLUDED.role,
                       permissions = EXCLUDED.permissions,
                       updated_at = EXCLUDED.updated_at""",
                auth_user.user_id,
                auth_user.email,
                profile.get("display_name") or auth_user.email,
                profile.get("username"),
                profile.get("first_name"),
                profile.get("last_name"),
                profile.get("phone"),
                profile.get("birth_date"),
                profile.get("country"),
                profile.get("city"),
                profile.get("locale"),
                role,
                json.dumps(permissions),
                datetime.now(timezone.utc),
            )
    except Exception:
        log.warning("auth.upsert_user_failed", user_id=auth_user.user_id, exc_info=True)

    # Create auth session
    session_id = __import__("uuid").uuid4().hex
    try:
        pool = await store._get_pool()
        async with pool.acquire() as conn:
            await conn.execute(
                """INSERT INTO user_auth_sessions
                   (id, user_id, access_token_enc, refresh_token_enc, expires_at, created_at, last_used_at)
                   VALUES ($1, $2, $3, $4, $5, $6, $6)""",
                session_id,
                auth_user.user_id,
                encryptor.encrypt(token_set.access_token),
                encryptor.encrypt(token_set.refresh_token or ""),
                token_set.expires_at or datetime.now(timezone.utc),
                datetime.now(timezone.utc),
            )
    except Exception:
        log.warning("auth.create_session_failed", user_id=auth_user.user_id, exc_info=True)

    # Retrieve platform/return_to info before state is forgotten
    state_info = _mobile_auth_states.pop(state, {})
    is_mobile = state_info.get("platform") == "android"
    return_to = state_info.get("return_to", "/")

    log.info("auth.login_success", user_id=auth_user.user_id, role=role, is_mobile=is_mobile)

    if is_mobile:
        # Mobile: redirect to bridge page that will deep-link back into the app.
        return Response(
            status_code=302,
            headers={"Location": f"/auth/mobile-done?sid={session_id}"},
        )

    # Browser: set httpOnly cookie and redirect.
    cookie_value = session_id
    max_age = settings.navi_auth_cookie_max_age_days * 86400
    cookie_str = (
        f"{settings.navi_auth_cookie_name}={cookie_value}; "
        f"Max-Age={max_age}; "
        f"HttpOnly; "
        f"Path=/; "
        f"SameSite={settings.navi_auth_cookie_samesite}"
    )
    if settings.navi_auth_cookie_secure:
        cookie_str += "; Secure"

    return Response(
        status_code=302,
        headers={
            "Location": return_to,
            "Set-Cookie": cookie_str,
        },
    )


@router.get("/mobile-done")
async def auth_mobile_done(sid: str) -> Response:
    """Bridge page for Android: receives the session id in the URL and
    deep-links back into the native app so CookieManager can set the auth
    cookie inside the WebView."""
    html = (
        "<!DOCTYPE html>"
        "<html>"
        '<head><meta charset=\"utf-8\"><title>Authentication complete</title></head>'
        "<body>"
        f'<script>window.location.href="navi://auth/callback?sid={sid}"</script>'
        "<p>Redirecting to app…</p>"
        "</body>"
        "</html>"
    )
    return Response(content=html, media_type="text/html")


@router.post("/logout")
async def auth_logout(response: Response, user: Annotated[User, Depends(require_user)], request: Request) -> dict:
    """Logout current user."""
    cookie_name = settings.navi_auth_cookie_name
    session_id = request.cookies.get(cookie_name)

    if session_id:
        try:
            from navi.api.deps import get_session_store
            store = get_session_store()
            pool = await store._get_pool()
            async with pool.acquire() as conn:
                await conn.execute("DELETE FROM user_auth_sessions WHERE id = $1", session_id)
        except Exception:
            log.warning("auth.logout_cleanup_failed", exc_info=True)

    # Clear cookie
    cookie_str = (
        f"{cookie_name}=; "
        f"Max-Age=0; "
        f"HttpOnly; "
        f"Path=/; "
        f"SameSite={settings.navi_auth_cookie_samesite}"
    )
    if settings.navi_auth_cookie_secure:
        cookie_str += "; Secure"

    response.headers["Set-Cookie"] = cookie_str
    log.info("auth.logout", user_id=user.id)
    return {"ok": True}


@router.get("/me")
async def auth_me(user: Annotated[User, Depends(require_user)]) -> dict:
    """Return current authenticated user."""
    profile_url = f"{settings.gnauth_base_url.rstrip('/')}{settings.gnauth_profile_path}"
    return {
        "id": user.id,
        "email": user.email,
        "display_name": user.display_name,
        "username": user.username,
        "first_name": user.first_name,
        "last_name": user.last_name,
        "phone": user.phone,
        "birth_date": user.birth_date,
        "country": user.country,
        "city": user.city,
        "locale": user.locale,
        "avatar_url": user.avatar_url,
        "role": user.role,
        "permissions": user.permissions,
        "profile_url": profile_url,
    }


@router.get("/status")
async def auth_status() -> dict:
    """Return whether OAuth is configured on the backend."""
    return {"configured": _auth_configured()}
