"""Authenticated dashboard operator grant minting.

Core owns dashboard authorization; Cloud owns the short-lived grant and all
records/logs/jobs data accessed with it.
"""
from __future__ import annotations

from typing import Annotated
from urllib.parse import quote
from uuid import UUID

import httpx
from fastapi import APIRouter, Depends, HTTPException, Request
from pydantic import BaseModel, Field
from sqlalchemy.ext.asyncio import AsyncSession

from src.core.database import get_db
from src.core.dependencies import require_active_billing_for_project_id
from src.domain.auth.models import User
from src.domain.auth.router import get_current_user
from src.domain.cloud.operate_catalog import admitted_operate_invocations
from src.domain.projects.repository import ProjectRepository, ProjectSettingsRepository
from src.integrations.cloud_control_client import get_cloud_client_for_workspace

router = APIRouter(prefix="/api/projects/{project_id}/operate", tags=["project-operate"])

ALLOWED_SCOPES = {"entities:read", "records:read", "logs:read", "jobs:read"}


class DashboardGrantRequest(BaseModel):
    scopes: list[str] = Field(default_factory=lambda: sorted(ALLOWED_SCOPES))
    ttl_seconds: int = Field(default=300, ge=30, le=900)


async def _dashboard_read(
    project_id: UUID,
    path: str,
    scope: str,
    query: dict[str, str],
    db: AsyncSession,
) -> dict:
    project = await ProjectRepository(db).get_by_id(project_id)
    if not project:
        raise HTTPException(status_code=404, detail="Project not found")
    cloud = await get_cloud_client_for_workspace(project.workspace_id, db)
    if not cloud._is_configured():
        raise HTTPException(status_code=503, detail="Cloud not configured")
    try:
        grant = await cloud.mint_dashboard_grant(
            str(project_id), scopes=[scope], ttl_seconds=120
        )
        token = grant.get("token")
        if not token:
            raise HTTPException(status_code=502, detail="Cloud grant was invalid")
        status_code, payload = await cloud.fetch_dashboard_read(
            path, grant_token=str(token), query=query
        )
    except HTTPException:
        raise
    except httpx.HTTPError as exc:
        raise HTTPException(status_code=502, detail="Cloud dashboard read failed") from exc
    except Exception as exc:
        raise HTTPException(status_code=503, detail="Cloud unavailable") from exc
    if status_code >= 400:
        raise HTTPException(status_code=status_code, detail=payload)
    return payload


@router.post("/grant")
async def mint_dashboard_grant(
    body: DashboardGrantRequest,
    project_id: UUID = Depends(require_active_billing_for_project_id),
    _: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
) -> dict:
    scopes = sorted(set(body.scopes))
    if not scopes or not set(scopes).issubset(ALLOWED_SCOPES):
        raise HTTPException(status_code=400, detail="invalid dashboard grant scopes")
    project = await ProjectRepository(db).get_by_id(project_id)
    if not project:
        raise HTTPException(status_code=404, detail="Project not found")
    cloud = await get_cloud_client_for_workspace(project.workspace_id, db)
    if not cloud._is_configured():
        raise HTTPException(status_code=503, detail="Cloud not configured")
    try:
        return await cloud.mint_dashboard_grant(
            str(project_id), scopes=scopes, ttl_seconds=body.ttl_seconds
        )
    except httpx.HTTPStatusError as exc:
        raise HTTPException(status_code=502, detail="Cloud rejected dashboard grant") from exc
    except Exception as exc:
        raise HTTPException(status_code=503, detail="Cloud unavailable") from exc


@router.get("/invocations")
async def operate_invocations(
    project_id: UUID = Depends(require_active_billing_for_project_id),
    _: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
) -> dict:
    """Admitted Cloud invocations for the Operate console. Payment ids are queue names."""
    settings = await ProjectSettingsRepository(db).get_or_create(project_id)
    return {"invocations": admitted_operate_invocations(settings.settings or {})}


@router.get("/entities")
async def dashboard_entities(
    project_id: UUID = Depends(require_active_billing_for_project_id),
    _: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
) -> dict:
    return await _dashboard_read(
        project_id=project_id,
        path=f"/v1/projects/{project_id}/entities", scope="entities:read", query={}, db=db
    )


@router.get("/records/{entity_key}")
async def dashboard_records(
    entity_key: str,
    request: Request,
    project_id: UUID = Depends(require_active_billing_for_project_id),
    _: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
) -> dict:
    allowed = {key: value for key, value in request.query_params.items()
               if key in {"limit", "cursor", "status", "filter"}}
    return await _dashboard_read(
        project_id=project_id,
        path=f"/v1/projects/{project_id}/entities/{quote(entity_key, safe='')}/records",
        scope="records:read", query=allowed, db=db
    )


@router.get("/logs")
async def dashboard_logs(
    request: Request,
    project_id: UUID = Depends(require_active_billing_for_project_id),
    _: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
) -> dict:
    allowed = {key: value for key, value in request.query_params.items()
               if key in {"limit", "cursor", "source", "level"}}
    return await _dashboard_read(
        project_id=project_id,
        path=f"/v1/projects/{project_id}/logs", scope="logs:read", query=allowed, db=db
    )


@router.get("/jobs/{job_id}")
async def dashboard_job(
    job_id: str,
    project_id: UUID = Depends(require_active_billing_for_project_id),
    _: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
) -> dict:
    return await _dashboard_read(
        project_id=project_id,
        path=f"/v1/projects/{project_id}/jobs/{quote(job_id, safe='')}", scope="jobs:read", query={}, db=db
    )
