"""Pydantic schemas for API request/response validation.

These schemas provide:
- Type-safe request validation
- Human-friendly error messages
- Consistent response structures
- OpenAPI schema generation
"""

from __future__ import annotations

from datetime import UTC, datetime

from pydantic import BaseModel, Field, ValidationError, field_validator

from core.common import JsonObject

# =============================================================================
# COMMON SCHEMAS
# =============================================================================


class SuccessResponse(BaseModel):
    """Standard success response wrapper."""

    success: bool = True
    request_id: str | None = Field(None, description="Request ID for correlation")
    timestamp: str = Field(
        default_factory=lambda: datetime.now(UTC).isoformat(),
        description="Response timestamp in ISO format",
    )


class ErrorDetail(BaseModel):
    """Detailed error information."""

    field: str | None = Field(None, description="Field name if validation error")
    message: str = Field(..., description="Human-readable error message")
    code: str | None = Field(None, description="Error code")


class ErrorResponse(BaseModel):
    """Standard error response wrapper."""

    success: bool = False
    error: str = Field(..., description="Human-readable error message")
    code: str = Field(..., description="Error code")
    request_id: str | None = Field(None, description="Request ID for correlation")
    details: JsonObject | list[ErrorDetail] | None = Field(
        None, description="Additional error details"
    )
    timestamp: str = Field(
        default_factory=lambda: datetime.now(UTC).isoformat(),
        description="Error timestamp in ISO format",
    )


# =============================================================================
# AGENT SCHEMAS
# =============================================================================


class AgentTurnRequest(BaseModel):
    """Request to execute an agent turn."""

    message: str = Field(..., description="User message", min_length=1, max_length=100000)
    thread_id: str | None = Field(None, description="Thread ID for conversation context")
    context: JsonObject = Field(default_factory=dict, description="Additional context")
    attachments: list[JsonObject] = Field(
        default_factory=list, description="File attachments (images, documents)"
    )
    pathway: str | None = Field(
        None, description="Override agent's default pathway for this turn"
    )

    @field_validator("pathway")
    @classmethod
    def validate_pathway(cls, v: str | None) -> str | None:
        """Normalize pathway override: strip whitespace, lowercase."""
        if v is None:
            return v
        v = v.strip().lower()
        if not v:
            return None
        return v


class AgentResponse(BaseModel):
    """Agent details response."""

    id: str
    name: str
    instructions: str = ""
    tools: list[str] = Field(default_factory=list)
    skills: list[str] = Field(default_factory=list)
    model: str = "reasoning"
    pathway: str | None = None
    max_steps: int = 10
    temperature: float = 0.7


class AgentListItem(BaseModel):
    """Agent list item (preview)."""

    id: str
    name: str
    instructions_preview: str | None = None
    tools: list[str] = Field(default_factory=list)
    skills: list[str] = Field(default_factory=list)
    model: str = "reasoning"
    max_steps: int = 10
    pathway: str | None = None


class AgentListResponse(SuccessResponse):
    """List agents response."""

    agents: list[AgentListItem]
    count: int


class AgentTurnResponse(SuccessResponse):
    """Agent turn response."""

    response: str = Field(..., description="Agent's response message")
    completed: bool = Field(..., description="Whether turn completed successfully")
    steps_taken: int = Field(..., description="Number of steps executed")
    agent_id: str = Field(..., description="Agent ID")
    thread_id: str = Field(..., description="Thread ID")
    pathway: str | None = Field(None, description="Pathway used")
    error: str | None = Field(None, description="Error message if failed")


# =============================================================================
# PATHWAY SCHEMAS
# =============================================================================


class PathwayRunRequest(BaseModel):
    """Request to run a pathway."""

    inputs: JsonObject = Field(default_factory=dict, description="Input values")


class PathwayResponse(SuccessResponse):
    """Pathway details response."""

    pathway: JsonObject
    meta: JsonObject | None = None


class PathwayListResponse(SuccessResponse):
    """List pathways response."""

    pathways: list[JsonObject]
    count: int


class PathwayRunResponse(SuccessResponse):
    """Pathway run response."""

    outputs: JsonObject = Field(default_factory=dict, description="Pathway outputs")
    execution_id: str | None = Field(None, description="Execution ID")
    status: str = Field(..., description="Execution status")
    duration_ms: float | None = Field(None, description="Execution duration in milliseconds")
    error: str | None = Field(None, description="Error message if failed")


class PathwayGraphResponse(SuccessResponse):
    """Compact graph payload for a pathway."""

    pathway_id: str = Field(..., description="Pathway ID")
    source: str | None = Field(None, description="Pathway source")
    node_count: int = Field(0, description="Number of nodes")
    nodes: list[JsonObject] = Field(default_factory=list, description="Graph nodes")
    connections: list[list[str]] = Field(
        default_factory=list, description="Graph connections as [from, to] pairs"
    )


# =============================================================================
# TOOL SCHEMAS
# =============================================================================


class ToolCallRequest(BaseModel):
    """Request to call a tool."""

    arguments: JsonObject = Field(default_factory=dict, description="Tool arguments")
    workspace_id: str | None = Field(None, description="Workspace ID")
    thread_id: str | None = Field(None, description="Thread ID")


class ToolCallResponse(SuccessResponse):
    """Tool call response."""

    tool: str = Field(..., description="Tool name")
    result: JsonObject = Field(..., description="Tool execution result")
    duration_ms: float | None = Field(None, description="Execution duration in milliseconds")


class ToolListItem(BaseModel):
    """Tool list item."""

    name: str
    category: str
    description: str
    parameters: JsonObject
    output_schema: JsonObject | None = None


class ToolListResponse(SuccessResponse):
    """List tools response."""

    tools: list[ToolListItem]
    count: int
    categories: list[str] = []


# =============================================================================
# SKILL SCHEMAS
# =============================================================================


class SkillListItem(BaseModel):
    """Skill list item."""

    id: str
    name: str
    description: str
    tags: list[str] = Field(default_factory=list)
    tools: list[str] = Field(default_factory=list)


class SkillListResponse(SuccessResponse):
    """List skills response."""

    skills: list[SkillListItem]
    count: int


class SkillResponse(SuccessResponse):
    """Skill details response."""

    id: str
    name: str
    description: str
    instructions: str
    tags: list[str] = Field(default_factory=list)
    tools: list[str] = Field(default_factory=list)
    pathways: list[str] = Field(default_factory=list)


# =============================================================================
# RUN SCHEMAS
# =============================================================================


class RunSummary(BaseModel):
    """Single run item."""

    run_id: str
    status: str
    started_at_ms: int | None = None
    finished_at_ms: int | None = None
    duration_ms: int | None = None
    error: str | None = None
    workspace_id: str | None = None
    doc_id: str | None = None


class RunListResponse(SuccessResponse):
    """List runs response."""

    runs: list[RunSummary]
    count: int


class RunDetailResponse(SuccessResponse):
    """Full run detail response."""

    run_id: str
    pathway_id: str | None = None
    pathway_name: str | None = None
    status: str
    started_at_ms: int | None = None
    finished_at_ms: int | None = None
    duration_ms: int | None = None
    inputs: JsonObject | None = None
    outputs: JsonObject | None = None
    error: str | None = None
    workspace_id: str | None = None
    doc_id: str | None = None
    node_count: int = 0
    event_count: int = 0


class RunSpanItem(BaseModel):
    """Single span within a run."""

    span_id: str | None = None
    parent_span_id: str | None = None
    kind: str | None = None
    name: str | None = None
    start_ms: int | None = None
    end_ms: int | None = None
    duration_ms: int | None = None
    status: str | None = None
    error: str | None = None
    attributes: JsonObject = Field(default_factory=dict)
    inputs: JsonObject | None = None
    outputs: JsonObject | None = None


class RunSpansResponse(SuccessResponse):
    """Run spans response."""

    run_id: str
    spans: list[RunSpanItem]
    count: int


class RunEventItem(BaseModel):
    """Single event within a run."""

    seq: int
    ts_ms: int
    type: str
    node_id: str | None = None
    payload: JsonObject = Field(default_factory=dict)


class RunEventsResponse(SuccessResponse):
    """Run events response."""

    run_id: str
    events: list[RunEventItem]
    count: int
    has_more: bool = False


# =============================================================================
# INFRASTRUCTURE SCHEMAS
# =============================================================================


class HealthResponse(BaseModel):
    """Health / liveness response."""

    status: str
    uptime_seconds: float | None = None


class ReadyResponse(BaseModel):
    """Readiness probe response."""

    status: str
    checks: dict[str, str] = Field(default_factory=dict)
    request_id: str | None = None


class ConfigValidateResponse(BaseModel):
    """Configuration validation response."""

    valid: bool
    errors: list[str] = Field(default_factory=list)
    deployment: JsonObject = Field(default_factory=dict)
    warnings: list[str] = Field(default_factory=list)
    config: JsonObject = Field(default_factory=dict)
    request_id: str | None = None


# =============================================================================
# WEBSOCKET FRAME SCHEMAS
# =============================================================================


class JsonRpcResponse(BaseModel):
    """JSON-RPC 2.0 success response frame."""

    jsonrpc: str = "2.0"
    id: str | int | None = None
    result: JsonObject = Field(default_factory=dict)


class JsonRpcErrorDetail(BaseModel):
    """Error payload inside a JSON-RPC error frame."""

    code: int
    message: str
    data: JsonObject | None = None


class JsonRpcError(BaseModel):
    """JSON-RPC 2.0 error response frame."""

    jsonrpc: str = "2.0"
    id: str | int | None = None
    error: JsonRpcErrorDetail


class JsonRpcNotification(BaseModel):
    """JSON-RPC 2.0 notification (server-initiated, no id)."""

    jsonrpc: str = "2.0"
    method: str
    params: JsonObject = Field(default_factory=dict)


class WsEventFrame(BaseModel):
    """Generic WebSocket event envelope."""

    type: str
    timestamp: str | None = None
    payload: JsonObject = Field(default_factory=dict)


# =============================================================================
# WEBHOOK SCHEMAS
# =============================================================================


class WebhookResponse(SuccessResponse):
    """Webhook publish response."""

    topic: str
    delivered: int


# =============================================================================
# VALIDATION HELPERS
# =============================================================================


def validate_request(
    model: type[BaseModel], data: JsonObject
) -> tuple[BaseModel | None, ErrorResponse | None]:
    """Validate request data against a Pydantic model.

    Returns
    -------
        Tuple of (validated_model, error_response)
        If validation fails, returns (None, ErrorResponse)
        If validation succeeds, returns (model_instance, None)
    """
    try:
        validated = model.model_validate(data)
        return validated, None
    except ValidationError as e:
        errors = [
            ErrorDetail(
                field=".".join(str(loc) for loc in err["loc"]),
                message=err["msg"],
                code="validation_error",
            )
            for err in e.errors()
        ]
    except Exception:
        errors = []

    error_msg = "Validation failed"
    if errors:
        error_msg = f"Validation failed: {errors[0].message}"
        if len(errors) > 1:
            error_msg += f" (and {len(errors) - 1} more error(s))"

    return None, ErrorResponse(
        error=error_msg,
        code="validation_error",
        details=errors if errors else None,
    )


__all__ = [
    "AgentListItem",
    "AgentListResponse",
    "AgentResponse",
    "AgentTurnRequest",
    "AgentTurnResponse",
    "ConfigValidateResponse",
    "ErrorDetail",
    "ErrorResponse",
    "HealthResponse",
    "JsonRpcError",
    "JsonRpcErrorDetail",
    "JsonRpcNotification",
    "JsonRpcResponse",
    "PathwayGraphResponse",
    "PathwayListResponse",
    "PathwayResponse",
    "PathwayRunRequest",
    "PathwayRunResponse",
    "ReadyResponse",
    "RunDetailResponse",
    "RunEventItem",
    "RunEventsResponse",
    "RunListResponse",
    "RunSpanItem",
    "RunSpansResponse",
    "RunSummary",
    "SkillListItem",
    "SkillListResponse",
    "SkillResponse",
    "SuccessResponse",
    "ToolCallRequest",
    "ToolCallResponse",
    "ToolListItem",
    "ToolListResponse",
    "WebhookResponse",
    "WsEventFrame",
    "validate_request",
]
