"""
cortex/api/models.py — Pydantic request/response models for the Cortex HTTP API.

All models are defined at module level (no ``from __future__ import annotations``)
to avoid Pydantic ForwardRef resolution failures.
"""

from typing import Any, Dict, List, Literal, Optional, Union

try:
    from pydantic import BaseModel, Field
    _PYDANTIC_AVAILABLE = True
except ImportError:
    _PYDANTIC_AVAILABLE = False

if _PYDANTIC_AVAILABLE:

    # ---- Shared / primitive models ----------------------------------------

    class MessageDict(BaseModel):
        """A single message in a conversation."""
        role: str = Field(..., description="'user' | 'assistant' | 'system'")
        content: str = Field(..., description="Message text", max_length=100000)

    # ---- Request models ---------------------------------------------------

    class RememberRequest(BaseModel):
        """Body for POST /api/v1/remember."""
        conversation: Union[List[MessageDict], str] = Field(
            ...,
            description="Conversation to extract memories from — list of messages or raw text",
        )
        session_id: Optional[str] = Field(
            None,
            description="Session identifier",
            min_length=1,
            max_length=256,
            pattern=r'^[a-zA-Z0-9_\-.:@]+$',
        )
        scope: str = Field("user", description="Memory scope")
        owner_id: Optional[str] = Field(None, description="Memory owner namespace", max_length=200, pattern=r'^[a-zA-Z0-9_\-.:@]+$')

    class RetrieveRequest(BaseModel):
        """Body for POST /api/v1/retrieve."""
        query: str = Field(..., description="Natural-language retrieval query", min_length=1, max_length=8000)
        token_budget: Optional[int] = Field(None, description="Max tokens for context window")
        mode: Literal["auto", "fast", "thorough", "lightweight", "agentic", "brain_graph"] = Field(
            "auto", description="Retrieval mode"
        )
        strategy: Optional[Literal["auto", "fast", "thorough", "lightweight", "agentic", "brain_graph"]] = Field(
            None, description="Deprecated alias for mode"
        )
        owner_id: Optional[str] = Field(None, description="Memory owner namespace", max_length=200, pattern=r'^[a-zA-Z0-9_\-.:@]+$')

    class AskRequest(BaseModel):
        """Body for POST /api/v1/ask."""
        query: str = Field(..., description="Natural-language question to answer from memory", min_length=1, max_length=8000)
        owner_id: Optional[str] = Field(None, description='Memory owner namespace', max_length=200, pattern=r'^[a-zA-Z0-9_\-.:@]+$')
        history: List[Dict[str, str]] = Field(
            default_factory=list,
            description="Optional conversation history — list of {role, content} dicts",
        )
        mode: str = Field(
            "fast",
            description=(
                "Retrieval mode: 'fast' (default, single-shot) or 'agentic' "
                "(multi-step loop for complex questions)"
            ),
        )
        max_steps: int = Field(
            5,
            description="Maximum retrieval steps for agentic mode (default 5, min 1).",
            ge=1,
        )

    class FeedbackRequest(BaseModel):
        """Body for POST /api/v1/feedback."""
        memory_id: str = Field(..., description="ID of the memory being rated")
        helpful: bool = Field(..., description="True = helpful, False = not helpful")
        context: Optional[str] = Field(None, description="Free-form context or comment")
        target_type: str = Field("claim", description="'claim' | 'cornerstone' | 'procedure'")

    class SealRequest(BaseModel):
        """Body for POST /api/v1/cornerstones/{id}/seal."""
        label: Optional[str] = Field(None, description="Human-readable label")
        content: Optional[str] = Field(None, description="Cornerstone content override", max_length=100000)

    class ForgetQueryParams(BaseModel):
        """Query params for DELETE /api/v1/memories/{id}."""
        hard: bool = Field(False, description="Hard delete (bypass soft-delete)")

    class BrainGraphBuildRequest(BaseModel):
        """Body for POST /api/v1/brain-graph/build."""
        source_path: Optional[str] = Field(
            None, description="Path to a file or directory to index"
        )
        text: Optional[str] = Field(
            None, description="Raw text to index (used when source_path is absent)"
        )
        source_id: Optional[str] = Field(
            None, description="Stable identifier for the text source"
        )

    class BrainGraphQueryRequest(BaseModel):
        """Body for POST /api/v1/brain-graph/query."""
        query: str = Field(..., description="Natural-language query", min_length=1, max_length=8000)
        max_depth: int = Field(3, ge=1, le=10, description="BFS traversal depth")
        limit: int = Field(10, ge=1, le=200, description="Max nodes to return")

    class SessionWakeRequest(BaseModel):
        """Body for POST /api/v1/sessions/wake (also /session/wake — deprecated)."""
        session_id: Optional[str] = Field(
            None,
            description="Optional custom session ID",
            min_length=1,
            max_length=256,
            pattern=r'^[a-zA-Z0-9_\-.:@]+$',
        )

    class SessionSleepRequest(BaseModel):
        """Body for POST /api/v1/sessions/sleep (also /session/sleep — deprecated)."""
        session_id: Optional[str] = Field(
            None,
            description="Session to close (uses current if absent)",
            min_length=1,
            max_length=256,
            pattern=r'^[a-zA-Z0-9_\-.:@]+$',
        )
        consolidate: bool = Field(
            False,
            description="When True, run SleepConsolidator before closing the session",
        )

    class SessionNapRequest(BaseModel):
        """Body for POST /api/v1/sessions/nap (also /session/nap — deprecated)."""
        session_id: str = Field(
            ...,
            description="Session to put into napping state",
            min_length=1,
            max_length=256,
            pattern=r'^[a-zA-Z0-9_\-.:@]+$',
        )

    class SessionNapWakeRequest(BaseModel):
        """Body for POST /api/v1/sessions/nap/wake (also /session/nap/wake — deprecated)."""
        session_id: str = Field(
            ...,
            description="Session to resume from nap",
            min_length=1,
            max_length=256,
            pattern=r'^[a-zA-Z0-9_\-.:@]+$',
        )

    class DreamRequest(BaseModel):
        """Body for POST /api/v1/dream (no required fields)."""
        owner_id: Optional[str] = Field(None, description="Owner scope override", max_length=200, pattern=r'^[a-zA-Z0-9_\-.:@]+$')

    class AdminBackupRequest(BaseModel):
        """Body for POST /api/v1/admin/backup."""
        output_path: Optional[str] = Field(
            None,
            description="Destination path for the backup file. Defaults to cortex.db.bak",
        )

    class AdminRestoreRequest(BaseModel):
        """Body for POST /api/v1/admin/restore."""
        backup_path: str = Field(..., description="Path to the backup file to restore from")

    class ExportRequest(BaseModel):
        """Body for POST /api/v1/export."""
        format: str = Field("json", description="Export format: json | csv | markdown")
        owner_id: str = Field("default", description="Owner ID to export", max_length=200, pattern=r"^[a-zA-Z0-9_\-.:@]+$")

    class ImportRequest(BaseModel):
        """Body for POST /api/v1/import (JSON body fields)."""
        format: str = Field("evaos", description="Import format: evaos | mem0")
        owner_id: str = Field("default", description="Target owner ID", max_length=200, pattern=r"^[a-zA-Z0-9_\-.:@]+$")
        dry_run: bool = Field(False, description="Validate without writing")

    class SealNewRequest(BaseModel):
        """Body for POST /api/v1/cornerstones (create new cornerstone)."""
        label: str = Field(..., description="Human-readable label")
        content: str = Field(..., description="Cornerstone content", max_length=100000)
        owner_id: Optional[str] = Field(None, description="Override owner_id", max_length=200, pattern=r'^[a-zA-Z0-9_\-.:@]+$')
        injection_order: int = Field(
            0,
            ge=0,
            description="Deterministic context injection position (lower injects first)",
        )

    class ReviseRequest(BaseModel):
        """Body for PUT /api/v1/cornerstones/{id}."""
        content: str = Field(..., description="Updated content", max_length=100000)
        reason: Optional[str] = Field(None, description="Reason for revision")

    class MergeEntityRequest(BaseModel):
        """Body for POST /api/v1/entities/{id}/merge."""
        target_entity_id: str = Field(..., description="Entity ID to merge into this one")

    class RetrieveExplainRequest(BaseModel):
        """Body for POST /api/v1/retrieve/explain."""
        query: str = Field(..., description="Natural-language retrieval query", min_length=1, max_length=8000)
        limit: int = Field(10, ge=1, le=200, description="Max results to return")

    class MemoryPatchRequest(BaseModel):
        category: Optional[str] = Field(None, description="New category tag")
        sensitivity: Optional[str] = Field(None, description="normal | private | secret")
        status: Optional[str] = Field(None, description="active | cooling | deprecated | archived")
        confidence: Optional[float] = Field(None, description="0.0–1.0")

    class MemorySearchRequest(BaseModel):
        """Body for POST /api/v1/memories/search."""
        query: str = Field(..., description="Semantic search query", min_length=1, max_length=8000)
        owner_id: Optional[str] = Field(None, description='Owner ID override', max_length=200, pattern=r'^[a-zA-Z0-9_\-.:@]+$')
        limit: int = Field(10, ge=1, le=200, description="Max results to return")
        top_k: Optional[int] = Field(None, ge=1, le=200, description="Deprecated alias for limit")
        filters: Optional[Dict[str, Any]] = Field(
            None, description="Optional filters: {category, status, entity_name}"
        )

    class EnhancedRetrieveRequest(BaseModel):
        """Body for enhanced POST /api/v1/retrieve."""
        query: str = Field(..., description="Natural-language retrieval query", min_length=1, max_length=8000)
        token_budget: Optional[int] = Field(None, description="Max tokens for context window")
        mode: Literal["auto", "fast", "thorough", "lightweight", "agentic", "brain_graph"] = Field(
            "auto", description="Retrieval mode"
        )
        strategy: Optional[Literal["auto", "fast", "thorough", "lightweight", "agentic", "brain_graph"]] = Field(
            None, description="Deprecated alias for mode"
        )
        explain: bool = Field(False, description="Include retrieval scoring details")
        owner_id: Optional[str] = Field(None, description="Memory owner namespace", max_length=200, pattern=r'^[a-zA-Z0-9_\-.:@]+$')
        before_date: Optional[str] = Field(
            None,
            description=(
                "ISO date string (YYYY-MM-DD). Exclude claims with event_date after this date. "
                "Claims without event_date are never excluded. "
                "When date filters are active, results are returned chronologically."
            ),
            pattern=r'^\d{4}-\d{2}-\d{2}$',
        )
        after_date: Optional[str] = Field(
            None,
            description=(
                "ISO date string (YYYY-MM-DD). Exclude claims with event_date before this date. "
                "Claims without event_date are never excluded. "
                "When date filters are active, results are returned chronologically."
            ),
            pattern=r'^\d{4}-\d{2}-\d{2}$',
        )

    class EnhancedRememberRequest(BaseModel):
        """Body for enhanced POST /api/v1/remember."""
        conversation: Union[List[MessageDict], str] = Field(
            ...,
            description="Conversation to extract memories from",
        )
        session_id: Optional[str] = Field(
            None,
            description="Session identifier",
            min_length=1,
            max_length=256,
            pattern=r'^[a-zA-Z0-9_\-.:@]+$',
        )
        scope: str = Field("user", description="Memory scope: user | agent | world")
        owner_id: Optional[str] = Field(None, description="Memory owner namespace", max_length=200, pattern=r'^[a-zA-Z0-9_\-.:@]+$')
        metadata: Optional[Dict[str, Any]] = Field(
            None, description="Additional metadata to attach to stored memories"
        )

    class UpdateExtractionInstructionsRequest(BaseModel):
        """Body for PUT /api/v1/extraction/instructions."""
        instructions: str = Field(
            ...,
            description="Custom extraction instructions (max 2000 chars)",
            max_length=2000,
        )

    class ExtractionTestRequest(BaseModel):
        """Body for POST /api/v1/extraction/test."""
        instructions: str = Field(
            ...,
            description="Custom extraction instructions (max 2000 chars)",
            max_length=2000,
        )
        sample_text: str

    class BrainBuildRequest(BaseModel):
        """Body for POST /api/v1/brain/build."""
        watch_dirs: List[str] = Field(default_factory=list, description="Directories to index")
        include_patterns: List[str] = Field(default_factory=list, description="Glob include patterns")
        exclude_patterns: List[str] = Field(default_factory=list, description="Glob exclude patterns")

    class BrainQueryRequest(BaseModel):
        """Body for POST /api/v1/brain/query."""
        query: str = Field(..., description="Natural-language query", min_length=1, max_length=8000)
        max_results: int = Field(10, ge=1, le=200)
        node_type: Optional[str] = Field(None, description="Filter by node type")

    class PeerCreateRequest(BaseModel):
        display_name: str
        entity_id: Optional[str] = None
        relationship: str = "unknown"
        owner_id: Optional[str] = None

    class PeerPatchRequest(BaseModel):
        display_name: Optional[str] = None
        entity_id: Optional[str] = None
        relationship: Optional[str] = None

    class PeerRebuildRequest(BaseModel):
        """Claims payload for representation rebuild."""
        claims: List[Dict[str, Any]] = Field(default_factory=list)

    # ---- Response models --------------------------------------------------

    class AskResponse(BaseModel):
        """Response for POST /api/v1/ask."""
        answer: str
        sources: List[Dict[str, Any]] = Field(default_factory=list)
        confidence: float = 0.0
        reasoning: str = ""
        ok: bool = True
        errors: List[str] = Field(default_factory=list)

    class RememberResponse(BaseModel):
        session_id: str
        claims_extracted: int
        claims_stored: int
        entities_resolved: int
        noise_filtered: int
        errors: List[str] = Field(default_factory=list)
        warnings: List[str] = Field(default_factory=list)
        zero_claim_warning: bool = False
        ok: bool

    class ForgetResponse(BaseModel):
        memory_id: str
        deleted: bool
        reason: str = ""
        errors: List[str] = Field(default_factory=list)

    class HealthResponse(BaseModel):
        ok: bool
        storage: str
        modules: Dict[str, str] = Field(default_factory=dict)
        errors: List[str] = Field(default_factory=list)
        timestamp: str

    class StatsResponse(BaseModel):
        total_claims: int = 0
        total_sessions: int = 0
        total_cornerstones: int = 0
        storage_ok: bool = False
        errors: List[str] = Field(default_factory=list)

    class BrainGraphBuildResponse(BaseModel):
        nodes_created: int = 0
        edges_created: int = 0
        graph_id: Optional[str] = None
        errors: List[str] = Field(default_factory=list)

    class BrainGraphQueryResponse(BaseModel):
        nodes: List[Dict[str, Any]] = Field(default_factory=list)
        edges: List[Dict[str, Any]] = Field(default_factory=list)
        context: str = ""
        token_count: int = 0
        errors: List[str] = Field(default_factory=list)

    class BrainGraphListResponse(BaseModel):
        nodes: List[Dict[str, Any]] = Field(default_factory=list)
        total: int = 0
        errors: List[str] = Field(default_factory=list)

    class SessionWakeResponse(BaseModel):
        session_id: str
        cornerstones_loaded: int = 0
        errors: List[str] = Field(default_factory=list)

    class SessionSleepResponse(BaseModel):
        session_id: str
        claims_consolidated: int = 0
        duration_ms: int = 0
        errors: List[str] = Field(default_factory=list)

    class SessionNapResponse(BaseModel):
        """Response for POST /api/v1/sessions/nap (also /session/nap — deprecated)."""
        status: str = "napping"
        session_id: str
        errors: List[str] = Field(default_factory=list)

    class SessionNapWakeResponse(BaseModel):
        """Response for POST /api/v1/sessions/nap/wake (also /session/nap/wake — deprecated)."""
        status: str = "active"
        session_id: str
        errors: List[str] = Field(default_factory=list)

    class DreamResponse(BaseModel):
        steps_completed: int = 0
        curiosity_seeds: int = 0
        claims_consolidated: int = 0
        journal: str = ""
        duration_ms: int = 0
        errors: List[str] = Field(default_factory=list)

    class LlmUsageResponse(BaseModel):
        """Response for GET /api/v1/llm/usage (#419)."""
        tiers: Dict[str, Any] = Field(default_factory=dict)
        by_operation: Dict[str, Any] = Field(default_factory=dict)
        total: Dict[str, Any] = Field(default_factory=dict)
        generated_at: str = ""
        errors: List[str] = Field(default_factory=list)

    class EntityListResponse(BaseModel):
        entities: List[Dict[str, Any]] = Field(default_factory=list)
        total: int = 0
        errors: List[str] = Field(default_factory=list)

    class AdminBackupResponse(BaseModel):
        path: str
        size_bytes: int
        timestamp: str

    class AdminRestoreResponse(BaseModel):
        restored: bool
        claims_count: int = 0
        errors: List[str] = Field(default_factory=list)

    class AdminStatsResponse(BaseModel):
        claims: Dict[str, Any] = Field(default_factory=dict)
        cornerstones: Dict[str, Any] = Field(default_factory=dict)
        entities: Dict[str, Any] = Field(default_factory=dict)
        sessions: Dict[str, Any] = Field(default_factory=dict)
        brain_graph: Dict[str, Any] = Field(default_factory=dict)
        storage: Dict[str, Any] = Field(default_factory=dict)
        errors: List[str] = Field(default_factory=list)

    class RequestLogResponse(BaseModel):
        items: List[Dict[str, Any]] = Field(default_factory=list)
        total: int = 0
        errors: List[str] = Field(default_factory=list)

    class MemoryBrowseResponse(BaseModel):
        items: List[Dict[str, Any]] = Field(default_factory=list)
        total: int = 0
        page: int = 1
        per_page: int = 50
        pages: int = 0
        category_counts: Dict[str, Any] = Field(default_factory=dict)
        errors: List[str] = Field(default_factory=list)

    class MemoryDetailResponse(BaseModel):
        memory: Optional[Dict[str, Any]] = None
        changelog: List[Dict[str, Any]] = Field(default_factory=list)
        related: List[Dict[str, Any]] = Field(default_factory=list)
        provenance: List[Dict[str, Any]] = Field(default_factory=list)
        errors: List[str] = Field(default_factory=list)

    class MemoryPatchResponse(BaseModel):
        memory: Optional[Dict[str, Any]] = None
        errors: List[str] = Field(default_factory=list)

    class CategoryListResponse(BaseModel):
        categories: List[Dict[str, Any]] = Field(default_factory=list)
        errors: List[str] = Field(default_factory=list)

    class DashboardStatsResponse(BaseModel):
        """Response for GET /api/v1/stats (enhanced)."""
        total_memories: int = 0
        active_count: int = 0
        cooling_count: int = 0
        deprecated_count: int = 0
        total_entities: int = 0
        total_sessions: int = 0
        total_cornerstones: int = 0
        avg_drift_score: float = 0.0
        category_counts: Dict[str, Any] = Field(default_factory=dict)
        memory_type_counts: Dict[str, Any] = Field(default_factory=dict)
        status_counts: Dict[str, Any] = Field(default_factory=dict)
        errors: List[str] = Field(default_factory=list)

    class RequestStatsResponse(BaseModel):
        """Response for GET /api/v1/stats/requests."""
        items: List[Dict[str, Any]] = Field(default_factory=list)
        time_range: str = "7d"
        granularity: str = "day"
        errors: List[str] = Field(default_factory=list)

    class MemoryTimeSeriesResponse(BaseModel):
        """Response for GET /api/v1/stats/memories."""
        items: List[Dict[str, Any]] = Field(default_factory=list)
        time_range: str = "30d"
        errors: List[str] = Field(default_factory=list)

    class MemorySearchResponse(BaseModel):
        """Response for POST /api/v1/memories/search."""
        items: List[Dict[str, Any]] = Field(default_factory=list)
        total: int = 0
        errors: List[str] = Field(default_factory=list)

    class EnhancedRememberResponse(BaseModel):
        """Response for enhanced POST /api/v1/remember."""
        session_id: str
        claims_extracted: int = 0
        claims_added: int = 0
        claims_updated: int = 0
        claims_stored: int = 0
        entities_resolved: int = 0
        noise_filtered: int = 0
        events_logged: int = 0
        processing_time_ms: int = 0
        errors: List[str] = Field(default_factory=list)
        warnings: List[str] = Field(default_factory=list)
        zero_claim_warning: bool = False
        ok: bool = True

    class RetrieveExplainResponse(BaseModel):
        results: List[Dict[str, Any]] = Field(default_factory=list)
        explanation: Dict[str, Any] = Field(default_factory=dict)
        errors: List[str] = Field(default_factory=list)

    class ExtractionInstructionsResponse(BaseModel):
        """Response for GET /api/v1/extraction/instructions."""
        instructions: str

    class ExtractionTestResponse(BaseModel):
        """Response for POST /api/v1/extraction/test."""
        claims: List[dict]
        claims_count: int
        noise_filtered: int

    class BrainBuildResponse(BaseModel):
        nodes_added: int = 0
        nodes_updated: int = 0
        edges_created: int = 0
        errors: List[str] = Field(default_factory=list)

    class BrainNodeConnection(BaseModel):
        node_id: str
        label: str
        relation: str = ""

    class BrainQueryNode(BaseModel):
        id: str
        title: str
        content_preview: str
        type: str
        relevance_score: float = 0.0
        connections: List[Dict[str, Any]] = Field(default_factory=list)

    class BrainQueryResponse(BaseModel):
        nodes: List[BrainQueryNode] = Field(default_factory=list)
        errors: List[str] = Field(default_factory=list)

    class BrainNodeListResponse(BaseModel):
        items: List[Dict[str, Any]] = Field(default_factory=list)
        total: int = 0
        errors: List[str] = Field(default_factory=list)

    class BrainNodeDetailResponse(BaseModel):
        id: str = ""
        title: str = ""
        content: str = ""
        type: str = ""
        source_path: Optional[str] = None
        connections: List[Dict[str, Any]] = Field(default_factory=list)
        created_at: Optional[str] = None
        updated_at: Optional[str] = None
        errors: List[str] = Field(default_factory=list)

    class BrainStatsResponse(BaseModel):
        total_nodes: int = 0
        total_edges: int = 0
        node_types: Dict[str, Any] = Field(default_factory=dict)
        last_build: Optional[str] = None
        errors: List[str] = Field(default_factory=list)
