"""Pydantic models for the multi-agent framework."""

from __future__ import annotations

import re
from typing import Any

from pydantic import BaseModel, ConfigDict, Field, ValidationInfo, field_validator, model_validator


SAFE_NAME_RE = re.compile(r"^[A-Za-z0-9_-]+$")


class AgentConfig(BaseModel):
    """Configuration for a loadable stateless agent."""

    model_config = ConfigDict(extra="forbid")

    name: str
    role: str
    description: str
    system_prompt: str
    input_schema_description: str
    output_schema_description: str
    input_schema: dict[str, Any] = Field(default_factory=dict)
    output_schema: dict[str, Any] = Field(default_factory=dict)
    metadata: dict[str, Any] = Field(default_factory=dict)

    @field_validator("name")
    @classmethod
    def validate_name(cls, value: str) -> str:
        if not SAFE_NAME_RE.fullmatch(value):
            raise ValueError("name must use only letters, numbers, underscores, or hyphens")
        return value

    @field_validator("system_prompt")
    @classmethod
    def validate_system_prompt(cls, value: str) -> str:
        if not value.strip():
            raise ValueError("system_prompt must not be empty")
        return value


class MeetingBrief(BaseModel):
    """Shared background for a meeting."""

    model_config = ConfigDict(extra="forbid")

    project_background: str = ""
    current_state: str = ""
    constraints: list[str] = Field(default_factory=list)
    success_criteria: list[str] = Field(default_factory=list)
    non_goals: list[str] = Field(default_factory=list)


class DecisionPacket(BaseModel):
    """Decision frame for a meeting."""

    model_config = ConfigDict(extra="forbid")

    decision_to_make: str
    options: list[str] = Field(default_factory=list)
    evaluation_dimensions: list[str] = Field(default_factory=list)
    required_questions: list[str] = Field(default_factory=list)
    risk_tolerance: str = ""
    time_horizon: str = ""

    @field_validator("decision_to_make")
    @classmethod
    def validate_decision_to_make(cls, value: str) -> str:
        if not value.strip():
            raise ValueError("decision_to_make must not be empty")
        return value


class ContextSource(BaseModel):
    """External context source for a meeting."""

    model_config = ConfigDict(extra="forbid")

    type: str
    path: str
    purpose: str
    include: list[str] = Field(default_factory=list)
    exclude: list[str] = Field(default_factory=list)
    max_files: int = 20
    max_chars_per_file: int = 4000

    @field_validator("type")
    @classmethod
    def validate_type(cls, value: str) -> str:
        if value not in {"file", "directory"}:
            raise ValueError("type must be 'file' or 'directory'")
        return value

    @field_validator("path", "purpose")
    @classmethod
    def validate_non_empty_text(cls, value: str, info: ValidationInfo) -> str:
        if not value.strip():
            raise ValueError(f"{info.field_name} must not be empty")
        return value

    @field_validator("max_files", "max_chars_per_file")
    @classmethod
    def validate_positive_limits(cls, value: int, info: ValidationInfo) -> int:
        if value <= 0:
            raise ValueError(f"{info.field_name} must be greater than zero")
        return value


class MeetingInput(BaseModel):
    """Structured input for a meeting run."""

    model_config = ConfigDict(extra="forbid")

    topic: str
    brief: MeetingBrief
    decision_packet: DecisionPacket
    context_sources: list[ContextSource] = Field(default_factory=list)

    @field_validator("topic")
    @classmethod
    def validate_topic(cls, value: str) -> str:
        if not value.strip():
            raise ValueError("topic must not be empty")
        return value


class ContextDocument(BaseModel):
    """A context document included in a meeting bundle."""

    model_config = ConfigDict(extra="forbid")

    document_id: str
    source_path: str
    source_type: str
    purpose: str
    excerpt: str
    char_count: int


class ContextBundle(BaseModel):
    """Collected context for a meeting."""

    model_config = ConfigDict(extra="forbid")

    summary: str
    documents: list[ContextDocument] = Field(default_factory=list)
    skipped_paths: list[dict[str, str]] = Field(default_factory=list)


class DocumentCitation(BaseModel):
    """A machine-verifiable citation to a context document."""

    model_config = ConfigDict(extra="forbid")

    document_id: str
    source_path: str
    quote: str = ""


class AgentTurnResult(BaseModel):
    """Normalized runtime output for a stateless agent turn."""

    model_config = ConfigDict(extra="forbid")

    agent_name: str
    response: str
    key_points: list[str] = Field(default_factory=list)
    risks: list[str] = Field(default_factory=list)
    recommendations: list[str] = Field(default_factory=list)
    citations: list[DocumentCitation] = Field(default_factory=list)


class PMDecision(BaseModel):
    """Constrained orchestration decision from the PM agent."""

    model_config = ConfigDict(extra="forbid")

    analysis: str
    next_action: str
    target_agent: str | None = None
    prompt_for_agent: str | None = None
    final_report: str | None = None

    @field_validator("next_action")
    @classmethod
    def validate_next_action(cls, value: str) -> str:
        if value not in {"CALL_AGENT", "FINISH"}:
            raise ValueError("next_action must be CALL_AGENT or FINISH")
        return value

    @model_validator(mode="after")
    def validate_action_fields(self) -> "PMDecision":
        if self.next_action == "CALL_AGENT":
            if not self.target_agent or not self.prompt_for_agent:
                raise ValueError("CALL_AGENT requires target_agent and prompt_for_agent")
        if self.next_action == "FINISH" and not self.final_report:
            raise ValueError("FINISH requires final_report")
        return self


class MeetingState(BaseModel):
    """Shared whiteboard state for a meeting."""

    model_config = ConfigDict(extra="forbid")

    topic: str
    participants: list[str]
    loop_count: int
    max_loops: int
    status: str
    summary: str
    key_points: list[str] = Field(default_factory=list)
    open_questions: list[str] = Field(default_factory=list)
    decisions: list[str] = Field(default_factory=list)
    latest_agent_outputs: dict[str, AgentTurnResult] = Field(default_factory=dict)
    event_log: list[dict[str, Any]] = Field(default_factory=list)
    meeting_input: MeetingInput
    context_bundle: ContextBundle

    @field_validator("status")
    @classmethod
    def validate_status(cls, value: str) -> str:
        allowed = {"running", "finishing", "finished", "forced_stop", "failed"}
        if value not in allowed:
            raise ValueError("status must be a valid meeting lifecycle state")
        return value

    @model_validator(mode="after")
    def validate_loop_bounds(self) -> "MeetingState":
        if self.loop_count > self.max_loops + 1:
            raise ValueError("loop_count must not exceed max_loops + 1")
        return self
