"""Desktop-auth HTTP surface.

Unauthenticated: start, exchange, handoff consume.
Web-session: complete (Dash after login).
Desktop-session or device-secret: mint browser handoff.
"""
from __future__ import annotations
from typing import Optional
from uuid import UUID

from fastapi import APIRouter, Depends, Header
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
from sqlalchemy.ext.asyncio import AsyncSession

from src.config import get_settings
from src.core.database import get_db
from src.core.exceptions import UnauthorizedError
from src.core.security.jwt import decode_desktop_session_token
from src.domain.auth.models import User
from src.domain.auth.repository import SessionRepository, UserRepository
from src.domain.auth.router import get_current_user
from src.domain.auth.schemas import magic_verify_success_envelope
from src.domain.auth.bootstrap_service import AuthBootstrapService
from src.domain.auth.mfa_repository import TotpSecretRepository, WebAuthnCredentialRepository
from src.domain.admin.repository import AdminRepository
from src.domain.billing.repository import SubscriptionRepository
from src.domain.desktop_auth.repository import DesktopAuthRequestRepository, DesktopHandoffTokenRepository
from src.domain.desktop_auth.schemas import (
    BrowserHandoffConsumeRequest,
    BrowserHandoffRequest,
    BrowserHandoffResponse,
    DesktopAuthCompleteRequest,
    DesktopAuthCompleteResponse,
    DesktopAuthExchangeRequest,
    DesktopAuthExchangeResponse,
    DesktopAuthStartRequest,
    DesktopAuthStartResponse,
)
from src.domain.desktop_auth.service import DesktopAuthService
from src.domain.devices.repository import DeviceRepository
from src.domain.workspaces.repository import (
    MembershipRepository,
    RoleRepository,
    WorkspaceInviteRepository,
    WorkspaceRepository,
)
from src.core.security.hashing import verify_token_hash

router = APIRouter(prefix="/api/desktop", tags=["desktop-auth"])
security = HTTPBearer(auto_error=False)


def _get_service(db: AsyncSession = Depends(get_db)) -> DesktopAuthService:
    settings = get_settings()
    return DesktopAuthService(
        requests=DesktopAuthRequestRepository(db),
        handoffs=DesktopHandoffTokenRepository(db),
        users=UserRepository(db),
        sessions=SessionRepository(db),
        app_url=settings.app_url,
    )


@router.post("/auth/start", response_model=DesktopAuthStartResponse, status_code=201)
async def desktop_auth_start(
    body: DesktopAuthStartRequest,
    svc: DesktopAuthService = Depends(_get_service),
) -> DesktopAuthStartResponse:
    result = await svc.start(app_name=body.app_name, os=body.os)
    return DesktopAuthStartResponse(**result)


@router.post("/auth/complete", response_model=DesktopAuthCompleteResponse)
async def desktop_auth_complete(
    body: DesktopAuthCompleteRequest,
    user: User = Depends(get_current_user),
    svc: DesktopAuthService = Depends(_get_service),
) -> DesktopAuthCompleteResponse:
    result = await svc.complete(user_code=body.user_code, user_id=user.id)
    return DesktopAuthCompleteResponse(**result)


@router.post("/auth/exchange", response_model=DesktopAuthExchangeResponse)
async def desktop_auth_exchange(
    body: DesktopAuthExchangeRequest,
    svc: DesktopAuthService = Depends(_get_service),
) -> DesktopAuthExchangeResponse:
    result = await svc.exchange(login_code=body.login_code)
    return DesktopAuthExchangeResponse(**result)


async def _resolve_handoff_user(
    *,
    db: AsyncSession,
    credentials: Optional[HTTPAuthorizationCredentials],
    x_device_id: Optional[str],
) -> UUID:
    """Accept a desktop_session JWT, or a device_secret with X-Device-Id."""
    if not credentials or not credentials.credentials:
        raise UnauthorizedError("Missing authorization")
    token = credentials.credentials.strip()
    user_id = decode_desktop_session_token(token)
    if user_id:
        return user_id
    if not x_device_id:
        raise UnauthorizedError("Desktop session or device credentials required")
    try:
        device_id = UUID(x_device_id)
    except ValueError:
        raise UnauthorizedError("Invalid device id")
    device = await DeviceRepository(db).get_by_id(device_id)
    if not device or not device.device_secret_hash or not device.user_id:
        raise UnauthorizedError("Invalid device credentials")
    if not verify_token_hash(token, device.device_secret_hash):
        raise UnauthorizedError("Invalid device credentials")
    if device.status == "revoked":
        raise UnauthorizedError("Device has been revoked")
    return device.user_id


@router.post("/browser-handoff", response_model=BrowserHandoffResponse)
async def mint_browser_handoff(
    body: BrowserHandoffRequest,
    credentials: Optional[HTTPAuthorizationCredentials] = Depends(security),
    x_device_id: Optional[str] = Header(default=None, alias="X-Device-Id"),
    db: AsyncSession = Depends(get_db),
    svc: DesktopAuthService = Depends(_get_service),
) -> BrowserHandoffResponse:
    user_id = await _resolve_handoff_user(db=db, credentials=credentials, x_device_id=x_device_id)
    result = await svc.mint_handoff(user_id=user_id, next_path=body.next)
    return BrowserHandoffResponse(**result)


@router.post("/browser-handoff/consume")
async def consume_browser_handoff(
    body: BrowserHandoffConsumeRequest,
    db: AsyncSession = Depends(get_db),
    svc: DesktopAuthService = Depends(_get_service),
):
    """Dash landing uses this to turn a one-time token into a normal web session."""
    tokens, user, next_path = await svc.consume_handoff(token=body.token)
    bootstrap = AuthBootstrapService(
        workspace_repo=WorkspaceRepository(db),
        membership_repo=MembershipRepository(db),
        role_repo=RoleRepository(db),
        subscription_repo=SubscriptionRepository(db),
        totp_repo=TotpSecretRepository(db),
        webauthn_repo=WebAuthnCredentialRepository(db),
        admin_repo=AdminRepository(db),
        invite_repo=WorkspaceInviteRepository(db),
    )
    payload = await bootstrap.build_bootstrap(user)
    envelope = magic_verify_success_envelope(tokens, payload)
    data = envelope.model_dump()
    data["next"] = next_path
    return data
