"""Devices domain - FastAPI routers.

Surfaces:
1. /api/devices/pair/*      — unauthenticated pairing (start/poll) + session-authenticated approve
2. /api/devices             — session-authenticated list/revoke of the current user's devices
3. /api/devices/{id}/*      — device-secret-authenticated heartbeat, bootstrap, runs, claim
4. /api/workspaces/{ws}/devices — session-authenticated visibility (not ownership)
"""
from __future__ import annotations
from datetime import datetime, timezone
from typing import Optional
from uuid import UUID

from fastapi import APIRouter, Depends, Path, Query, Request
from sqlalchemy.ext.asyncio import AsyncSession

from src.config import get_settings
from src.core.database import async_session_factory, get_db
from src.core.exceptions import ForbiddenError, NotFoundError
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.auth.models import User
from src.domain.auth.router import get_current_user
from src.domain.billing.repository import SubscriptionRepository
from src.domain.codegen.repository import CodegenRunRepository
from src.domain.codegen.schemas import CodegenRunSummary
from src.domain.devices.claim_service import DeviceClaimService
from src.domain.devices.dependencies import require_active_device
from src.domain.devices.models import Device
from src.domain.devices.repository import DeviceEventRepository, DeviceRepository
from src.domain.devices.schemas import (
    ClaimRunResponse,
    DeviceBootstrapResponse,
    DeviceResponse,
    HeartbeatRequest,
    HeartbeatResponse,
    PairApproveRequest,
    PairPendingResponse,
    PairPollResponse,
    PairStartRequest,
    PairStartResponse,
)
from src.domain.devices.service import DeviceService
from src.domain.projects.repository import ProjectRepository
from src.domain.workspaces.repository import (
    MembershipRepository,
    RoleRepository,
    WorkspaceInviteRepository,
    WorkspaceRepository,
)

router = APIRouter(prefix="/api/devices", tags=["devices"])
workspace_router = APIRouter(prefix="/api/workspaces/{ws}/devices", tags=["devices"])

_RUN_STATUS_GROUPS = {
    "queued": ("queued", "pending"),
    "active": ("running", "planning", "generating", "applying", "validating", "preview_refreshing"),
}


def _get_service(db: AsyncSession = Depends(get_db)) -> DeviceService:
    return DeviceService(
        devices=DeviceRepository(db),
        events=DeviceEventRepository(db),
        app_url=get_settings().app_url,
    )


def _device_response(device: Device) -> DeviceResponse:
    return DeviceResponse.model_validate(device)


@router.post("/pair/start", response_model=PairStartResponse, status_code=201)
async def pair_start(
    body: PairStartRequest,
    svc: DeviceService = Depends(_get_service),
) -> PairStartResponse:
    result = await svc.start_pairing(
        name=body.name,
        hostname=body.hostname,
        os=body.os,
        os_version=body.os_version,
        app_version=body.app_version,
    )
    return PairStartResponse(**result)


@router.post("/pair/{device_code}/poll", response_model=PairPollResponse)
async def pair_poll(
    device_code: str = Path(...),
    svc: DeviceService = Depends(_get_service),
) -> PairPollResponse:
    result = await svc.poll_pairing(device_code)
    return PairPollResponse(**result)


@router.get("/pair/pending", response_model=PairPendingResponse)
async def pair_pending(
    user_code: str = Query(...),
    user: User = Depends(get_current_user),
    svc: DeviceService = Depends(_get_service),
) -> PairPendingResponse:
    device = await svc.get_pending_by_user_code(user_code)
    _ = user
    return PairPendingResponse(
        device_id=device.id,
        user_code=device.user_code or user_code,
        name=device.name,
        hostname=device.hostname,
        os=device.os,
        os_version=device.os_version,
        app_version=device.app_version,
        expires_at=device.pairing_expires_at,
    )


@router.post("/pair/approve", response_model=DeviceResponse)
async def pair_approve(
    body: PairApproveRequest,
    user: User = Depends(get_current_user),
    svc: DeviceService = Depends(_get_service),
) -> DeviceResponse:
    device = await svc.approve_pairing(user_code=body.user_code, user_id=user.id)
    return _device_response(device)


@router.get("", response_model=list[DeviceResponse])
@router.get("/", response_model=list[DeviceResponse])
async def list_my_devices(
    user: User = Depends(get_current_user),
    svc: DeviceService = Depends(_get_service),
) -> list[DeviceResponse]:
    devices = await svc.list_for_user(user.id)
    return [_device_response(d) for d in devices]


@router.delete("/{device_id}", response_model=DeviceResponse)
async def revoke_my_device(
    device_id: UUID = Path(...),
    user: User = Depends(get_current_user),
    svc: DeviceService = Depends(_get_service),
) -> DeviceResponse:
    device = await svc.revoke_own_device(user.id, device_id)
    return _device_response(device)


@router.post("/{device_id}/heartbeat", response_model=HeartbeatResponse)
async def device_heartbeat(
    body: HeartbeatRequest,
    request: Request,
    device: Device = Depends(require_active_device),
    svc: DeviceService = Depends(_get_service),
) -> HeartbeatResponse:
    client_ip = request.client.host if request.client else None
    await svc.heartbeat(device, capabilities=body.capabilities, last_seen_ip=client_ip)
    return HeartbeatResponse(status="ok", server_time=datetime.now(timezone.utc))


@router.post("/{device_id}/bootstrap", response_model=DeviceBootstrapResponse)
async def device_bootstrap(
    device: Device = Depends(require_active_device),
    db: AsyncSession = Depends(get_db),
) -> DeviceBootstrapResponse:
    if not device.user_id:
        raise NotFoundError("Device is not paired to a user")
    from src.domain.auth.repository import UserRepository

    user = await UserRepository(db).get_by_id(device.user_id)
    if not user:
        raise NotFoundError("User not found")
    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)
    return DeviceBootstrapResponse(
        device=_device_response(device),
        user=payload["user"],
        workspaces=list(payload["workspaces"]),
    )


@router.get("/{device_id}/runs", response_model=list[CodegenRunSummary])
async def list_device_runs(
    device_id: UUID = Path(...),
    status: Optional[str] = Query(None, description="queued | active"),
    _device: Device = Depends(require_active_device),
    db: AsyncSession = Depends(get_db),
) -> list[CodegenRunSummary]:
    statuses = list(_RUN_STATUS_GROUPS.get(status, ())) if status else None
    runs = await CodegenRunRepository(db).list_for_device(device_id, statuses=statuses)
    return [CodegenRunSummary.from_run(r) for r in runs]


@router.post("/{device_id}/runs/{run_id}/claim", response_model=ClaimRunResponse)
async def claim_device_run(
    run_id: UUID = Path(...),
    device: Device = Depends(require_active_device),
    db: AsyncSession = Depends(get_db),
) -> ClaimRunResponse:
    svc = DeviceClaimService(
        runs=CodegenRunRepository(db),
        devices=DeviceRepository(db),
        projects=ProjectRepository(db),
        db_factory=async_session_factory,
    )
    result = await svc.claim(device=device, run_id=run_id)
    return ClaimRunResponse(**result)


@workspace_router.get("", response_model=list[DeviceResponse])
async def list_workspace_devices(
    ws: UUID = Path(...),
    user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
    svc: DeviceService = Depends(_get_service),
) -> list[DeviceResponse]:
    membership = await MembershipRepository(db).get(ws, user.id)
    if not membership:
        raise ForbiddenError("You are not a member of this workspace")
    devices = await svc.list_recent_for_workspace(ws)
    return [_device_response(d) for d in devices]
