"""API key management routes for integrations."""
from __future__ import annotations

import uuid
from datetime import datetime, timezone
from typing import List, Optional

from fastapi import APIRouter, Depends, Header, HTTPException, Query, status
from pydantic import BaseModel, Field
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from normbase.api.deps_auth import RequireInternalToken
from normbase.db.connection import get_db
from normbase.models.norm import APIKey
from security.auth.api_keys import (
    generate_api_key,
    hash_api_key,
    mask_api_key,
    verify_api_key_hash,
)

router = APIRouter(prefix="/keys", tags=["keys"])
DEFAULT_SCOPES = [
    "roles:suggest",
    "norms:retrieve",
    "norms:check",
    "norms:report",
]


def _utc_now() -> datetime:
    return datetime.now(timezone.utc).replace(tzinfo=None)


def _to_utc(dt: datetime) -> datetime:
    if dt.tzinfo is None:
        return dt
    return dt.astimezone(timezone.utc).replace(tzinfo=None)


class APIKeyIssueRequest(BaseModel):
    agent_id: uuid.UUID
    name: Optional[str] = None
    expires_at: Optional[datetime] = None
    scopes: List[str] = Field(default_factory=list)


class APIKeyIssueResponse(BaseModel):
    key_id: str
    agent_id: uuid.UUID
    name: Optional[str] = None
    api_key: str
    key_preview: str
    scopes: List[str]
    is_active: bool
    expires_at: Optional[datetime] = None
    created_at: datetime


class APIKeyListItem(BaseModel):
    key_id: str
    agent_id: Optional[uuid.UUID] = None
    name: Optional[str] = None
    key_preview: str
    scopes: List[str]
    is_active: bool
    expires_at: Optional[datetime] = None
    created_at: datetime


class APIKeyResolveRequest(BaseModel):
    api_key: str = Field(..., min_length=8)


class APIKeyResolveResponse(BaseModel):
    key_id: str
    agent_id: uuid.UUID
    scopes: List[str]
    is_active: bool
    expires_at: Optional[datetime] = None


def _ensure_not_expired(record: APIKey) -> bool:
    if record.expires_at is None:
        return True
    return _to_utc(record.expires_at) > _utc_now()


def _normalize_scopes(scopes: Optional[List[str]]) -> List[str]:
    normalized: List[str] = []
    for scope in scopes or []:
        text = str(scope).strip()
        if text and text not in normalized:
            normalized.append(text)
    return normalized or list(DEFAULT_SCOPES)


def _extract_bearer(authorization: Optional[str]) -> str:
    if not authorization:
        return ""
    raw = authorization.strip()
    if raw.lower().startswith("bearer "):
        return raw[7:].strip()
    return raw


def _agent_uuid_or_500(agent_id_text: Optional[str]) -> uuid.UUID:
    text = (agent_id_text or "").strip()
    if not text:
        raise HTTPException(status_code=500, detail="Stored API key missing agent_id UUID.")
    try:
        return uuid.UUID(text)
    except ValueError as exc:
        raise HTTPException(status_code=500, detail="Stored API key has invalid agent_id UUID.") from exc


async def _resolve_api_key_from_plain(
    plain_api_key: str,
    db: AsyncSession,
) -> APIKey:
    hashed = hash_api_key(plain_api_key)
    row = (
        await db.execute(select(APIKey).where(APIKey.key_hash == hashed))
    ).scalar_one_or_none()
    if not row:
        raise HTTPException(status_code=401, detail="Invalid API key")
    if not row.is_active or not _ensure_not_expired(row):
        raise HTTPException(status_code=401, detail="API key inactive or expired")
    if not verify_api_key_hash(plain_api_key, row.key_hash):
        raise HTTPException(status_code=401, detail="Invalid API key")
    return row


@router.post(
    "/issue",
    response_model=APIKeyIssueResponse,
    status_code=status.HTTP_201_CREATED,
    dependencies=[RequireInternalToken],
)
async def issue_api_key(
    request: APIKeyIssueRequest,
    db: AsyncSession = Depends(get_db),
):
    normalized_scopes = _normalize_scopes(request.scopes)
    plain = generate_api_key()
    record = APIKey(
        key_hash=hash_api_key(plain),
        name=request.name or f"agent:{request.agent_id}",
        agent_id=str(request.agent_id),
        scopes=normalized_scopes,
        is_active=True,
        expires_at=request.expires_at,
    )
    db.add(record)
    await db.commit()
    await db.refresh(record)
    return APIKeyIssueResponse(
        key_id=str(record.id),
        agent_id=_agent_uuid_or_500(record.agent_id),
        name=record.name,
        api_key=plain,
        key_preview=mask_api_key(plain),
        scopes=list(record.scopes or []),
        is_active=record.is_active,
        expires_at=record.expires_at,
        created_at=record.created_at,
    )


@router.get("", response_model=List[APIKeyListItem], dependencies=[RequireInternalToken])
async def list_api_keys(
    agent_id: Optional[uuid.UUID] = Query(None, description="Filter by agent_id (UUID)"),
    include_inactive: bool = Query(False),
    db: AsyncSession = Depends(get_db),
):
    query = select(APIKey).order_by(APIKey.created_at.desc())
    if agent_id:
        query = query.where(APIKey.agent_id == str(agent_id))
    if not include_inactive:
        query = query.where(APIKey.is_active.is_(True))
    rows = (await db.execute(query)).scalars().all()
    return [
        APIKeyListItem(
            key_id=str(item.id),
            agent_id=_agent_uuid_or_500(item.agent_id) if item.agent_id else None,
            name=item.name,
            key_preview="***",
            scopes=list(item.scopes or []),
            is_active=item.is_active,
            expires_at=item.expires_at,
            created_at=item.created_at,
        )
        for item in rows
    ]


@router.post("/{key_id}/revoke", response_model=APIKeyListItem, dependencies=[RequireInternalToken])
async def revoke_api_key(
    key_id: uuid.UUID,
    db: AsyncSession = Depends(get_db),
):
    row = (await db.execute(select(APIKey).where(APIKey.id == key_id))).scalar_one_or_none()
    if not row:
        raise HTTPException(status_code=404, detail="API key not found")
    row.is_active = False
    await db.commit()
    await db.refresh(row)
    return APIKeyListItem(
        key_id=str(row.id),
        agent_id=_agent_uuid_or_500(row.agent_id) if row.agent_id else None,
        name=row.name,
        key_preview="***",
        scopes=list(row.scopes or []),
        is_active=row.is_active,
        expires_at=row.expires_at,
        created_at=row.created_at,
    )


@router.post("/{key_id}/rotate", response_model=APIKeyIssueResponse, dependencies=[RequireInternalToken])
async def rotate_api_key(
    key_id: uuid.UUID,
    db: AsyncSession = Depends(get_db),
):
    row = (await db.execute(select(APIKey).where(APIKey.id == key_id))).scalar_one_or_none()
    if not row:
        raise HTTPException(status_code=404, detail="API key not found")
    plain = generate_api_key()
    row.key_hash = hash_api_key(plain)
    row.is_active = True
    await db.commit()
    await db.refresh(row)
    return APIKeyIssueResponse(
        key_id=str(row.id),
        agent_id=_agent_uuid_or_500(row.agent_id),
        name=row.name,
        api_key=plain,
        key_preview=mask_api_key(plain),
        scopes=list(row.scopes or []),
        is_active=row.is_active,
        expires_at=row.expires_at,
        created_at=row.created_at,
    )


@router.post("/resolve", response_model=APIKeyResolveResponse, dependencies=[RequireInternalToken])
async def resolve_api_key(
    request: APIKeyResolveRequest,
    db: AsyncSession = Depends(get_db),
):
    row = await _resolve_api_key_from_plain(request.api_key, db)
    return APIKeyResolveResponse(
        key_id=str(row.id),
        agent_id=_agent_uuid_or_500(row.agent_id),
        scopes=list(row.scopes or []),
        is_active=row.is_active,
        expires_at=row.expires_at,
    )


@router.get("/introspect", response_model=APIKeyResolveResponse)
async def introspect_api_key(
    authorization: Optional[str] = Header(default=None),
    x_api_key: Optional[str] = Header(default=None),
    db: AsyncSession = Depends(get_db),
):
    plain = (x_api_key or "").strip() or _extract_bearer(authorization)
    if not plain:
        raise HTTPException(status_code=401, detail="Missing API key")
    row = await _resolve_api_key_from_plain(plain, db)
    return APIKeyResolveResponse(
        key_id=str(row.id),
        agent_id=_agent_uuid_or_500(row.agent_id),
        scopes=list(row.scopes or []),
        is_active=row.is_active,
        expires_at=row.expires_at,
    )

