"""
Pydantic v2 models for API key management (Caller Types 2 & 3).
"""

from __future__ import annotations

from datetime import datetime
from uuid import UUID

from pydantic import BaseModel, Field, field_validator

VALID_SCOPES: frozenset[str] = frozenset({
    "data:push",       # publish data to push feed
    "data:pull",       # query REST pull endpoints
    "feed:subscribe",  # subscribe to WebSocket push stream
    "internal:job",    # internal cron / scheduled jobs
})


class ApiKeyCreate(BaseModel):
    """Request body for POST /admin/api-keys."""
    service_name: str = Field(..., min_length=1, max_length=128)
    scopes: list[str] = Field(..., min_length=1)
    rate_limit: int = Field(600, ge=1, le=10_000, description="Max requests/minute")
    description: str | None = Field(None, max_length=512)
    expires_at: datetime | None = Field(None, description="NULL = never expires")

    @field_validator("scopes")
    @classmethod
    def validate_scopes(cls, v: list[str]) -> list[str]:
        invalid = set(v) - VALID_SCOPES
        if invalid:
            raise ValueError(
                f"Invalid scopes: {sorted(invalid)}. "
                f"Valid values: {sorted(VALID_SCOPES)}"
            )
        return sorted(set(v))  # deduplicate + stable sort


class ApiKeyResponse(BaseModel):
    """Safe representation — never includes secret_hash or plain secret."""
    key_id: UUID
    service_name: str
    scopes: list[str]
    is_active: bool
    rate_limit: int
    description: str | None
    expires_at: datetime | None
    created_at: datetime
    last_used_at: datetime | None

    model_config = {"from_attributes": True}


class ApiKeyCreated(ApiKeyResponse):
    """
    Returned ONLY at key creation time.
    The `secret` field is the plain-text value the subscriber must store.
    It is NEVER retrievable again after this response.
    """
    secret: str = Field(
        ...,
        description="Store this securely. Not retrievable after creation.",
    )


class ApiKeyUpdate(BaseModel):
    """Request body for PATCH /admin/api-keys/{key_id}."""
    is_active: bool = False
    reason: str | None = Field(None, max_length=256)


class ApiKeyRotated(ApiKeyResponse):
    """
    Returned by POST /admin/api-keys/{key_id}/rotate.

    The new plain secret is shown ONCE — store it immediately.
    The previous secret is invalidated the moment this response is sent.
    """
    new_secret: str = Field(
        ...,
        description="New plain-text secret. Store securely — not retrievable again.",
    )
    rotated_at: datetime
    rotation_reason: str | None = Field(None, description="Admin-supplied reason for audit trail.")


class ApiKeySecretResponse(BaseModel):
    """
    Returned by GET /admin/api-keys/{key_id}/secret.

    Only available for HMAC keys (Fernet-encrypted storage).
    Cron keys use bcrypt — one-way hash, secret is not recoverable.
    """
    key_id: UUID
    service_name: str
    scopes: list[str]
    key_type: str = Field(
        ...,
        description="'hmac' — secret recoverable · 'cron' — secret not recoverable (bcrypt)",
    )
    secret: str = Field(
        ...,
        description="Plain-text secret. Treat as sensitive — log access via audit trail.",
    )
    revealed_at: datetime
