"""
AwoooP Contract Pydantic Models
================================
Phase 3: 六合約家族 Pydantic v2 驗證模型（ADR-112）
2026-05-04 ogt + Claude Sonnet 4.6

六合約家族：
  1. ProjectTenantContract     — 租戶/專案能力邊界
  2. AgentContract             — Agent 模型、工具、治理
  3. MCPGatewayContract        — MCP 工具閘道
  4. PolicyRoutingContract     — LLM 路由規則
  5. RuntimeRunStateContract   — Run FSM 狀態
  6. ChannelEventContract      — Channel 事件（冪等）

所有含 artifact ref 的欄位都附 sha256（ADR-112 artifact integrity）。
"""

from __future__ import annotations

import re
from datetime import datetime
from enum import Enum
from typing import Any
from uuid import UUID

from pydantic import BaseModel, Field, field_validator, model_validator


# ─────────────────────────────────────────────────────────────────────────────
# 共用型別
# ─────────────────────────────────────────────────────────────────────────────

_SHA256_RE = re.compile(r"^[0-9a-f]{64}$")
_PROJECT_ID_RE = re.compile(r"^[a-z0-9][a-z0-9_-]{1,63}$")
_AGENT_ID_RE = re.compile(r"^[a-z0-9][a-z0-9_-]{1,127}$")
_UUID_RE = re.compile(
    r"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
)


def _validate_sha256(v: str | None, field_name: str = "sha256") -> str | None:
    if v is None:
        return v
    if not _SHA256_RE.match(v):
        raise ValueError(f"{field_name} 必須為 64 位 hex 字串")
    return v


class MigrationMode(str, Enum):
    LEGACY = "legacy_awoooi_default"
    SHADOW = "shadow"
    CANARY = "canary"
    ACTIVE = "active"


class ChannelType(str, Enum):
    TELEGRAM = "telegram"
    SLACK = "slack"
    WEBHOOK = "webhook"
    API = "api"


class Provider(str, Enum):
    ANTHROPIC = "anthropic"
    OPENAI = "openai"
    OLLAMA = "ollama"
    GEMINI = "gemini"
    NVIDIA = "nvidia"
    OPENROUTER = "openrouter"


class RunState(str, Enum):
    PENDING = "pending"
    RUNNING = "running"
    WAITING_APPROVAL = "waiting_approval"
    WAITING_TOOL = "waiting_tool"
    COMPLETED = "completed"
    FAILED = "failed"
    CANCELLED = "cancelled"
    TIMEOUT = "timeout"


class AuthScheme(str, Enum):
    NONE = "none"
    BEARER = "bearer"
    HMAC = "hmac"


class Transport(str, Enum):
    STDIO = "stdio"
    HTTP = "http"
    SSE = "sse"


class EventType(str, Enum):
    MESSAGE_RECEIVED = "message_received"
    CALLBACK_QUERY = "callback_query"
    COMMAND_INVOKED = "command_invoked"
    WEBHOOK_POST = "webhook_post"
    API_REQUEST = "api_request"
    APPROVAL_RESPONSE = "approval_response"


# ─────────────────────────────────────────────────────────────────────────────
# 1. Project Tenant Contract
# ─────────────────────────────────────────────────────────────────────────────

class ProjectTenantContract(BaseModel):
    """租戶/專案合約（ADR-111/115）"""

    model_config = {"extra": "forbid"}

    project_id: str = Field(..., description="全局唯一租戶識別符")
    display_name: str = Field(..., min_length=1, max_length=256)
    migration_mode: MigrationMode = MigrationMode.LEGACY
    budget_limit_usd: float | None = Field(None, ge=0)
    allowed_channels: list[ChannelType] = Field(default_factory=list)
    is_active: bool = True
    metadata: dict[str, Any] = Field(default_factory=dict)

    @field_validator("project_id")
    @classmethod
    def validate_project_id(cls, v: str) -> str:
        if not _PROJECT_ID_RE.match(v):
            raise ValueError("project_id 只允許 a-z, 0-9, _, -，長度 2-64")
        return v

    @field_validator("allowed_channels")
    @classmethod
    def validate_unique_channels(cls, v: list[ChannelType]) -> list[ChannelType]:
        if len(v) != len(set(v)):
            raise ValueError("allowed_channels 不可包含重複項目")
        return v


# ─────────────────────────────────────────────────────────────────────────────
# 2. Agent Contract
# ─────────────────────────────────────────────────────────────────────────────

class ArtifactRef(BaseModel):
    """含 SHA-256 的 artifact 參照（ADR-112 artifact integrity）"""

    model_config = {"extra": "forbid"}

    artifact_id: str
    sha256: str = Field(..., description="SHA-256 hex digest（64 位）")

    @field_validator("sha256")
    @classmethod
    def validate_sha256(cls, v: str) -> str:
        return _validate_sha256(v, "sha256")  # type: ignore[return-value]


class ToolRef(BaseModel):
    """Agent 工具參照"""

    model_config = {"extra": "allow"}

    tool_name: str
    mcp_gateway_id: str | None = None
    sha256: str | None = None

    @field_validator("sha256")
    @classmethod
    def validate_sha256(cls, v: str | None) -> str | None:
        return _validate_sha256(v, "tool sha256")


class AgentContract(BaseModel):
    """Agent 合約（ADR-112）"""

    model_config = {"extra": "forbid"}

    agent_id: str = Field(..., description="Agent 識別符")
    agent_name: str = Field(..., min_length=1, max_length=256)
    model: str = Field(..., min_length=1, max_length=128)
    provider: Provider
    max_tokens: int | None = Field(None, ge=1, le=200000)
    temperature: float | None = Field(None, ge=0.0, le=2.0)
    system_prompt_ref: ArtifactRef | None = None
    tools: list[ToolRef] = Field(default_factory=list)
    budget_limit_usd_per_run: float | None = Field(None, ge=0)
    require_approval: bool = False
    approval_timeout_seconds: int | None = Field(None, ge=60, le=86400)
    max_parallel_runs: int = Field(1, ge=1, le=100)
    tags: list[str] = Field(default_factory=list)

    @field_validator("agent_id")
    @classmethod
    def validate_agent_id(cls, v: str) -> str:
        if not _AGENT_ID_RE.match(v):
            raise ValueError("agent_id 只允許 a-z, 0-9, _, -，長度 2-128")
        return v

    @model_validator(mode="after")
    def validate_approval_config(self) -> AgentContract:
        if self.require_approval and self.approval_timeout_seconds is None:
            self.approval_timeout_seconds = 300
        return self


# ─────────────────────────────────────────────────────────────────────────────
# 3. MCP Gateway Contract
# ─────────────────────────────────────────────────────────────────────────────

class ToolExposed(BaseModel):
    """Gateway 暴露的工具定義"""

    model_config = {"extra": "forbid"}

    tool_name: str
    description: str | None = None
    schema_sha256: str = Field(..., description="工具 input schema SHA-256")
    is_destructive: bool = False

    @field_validator("schema_sha256")
    @classmethod
    def validate_schema_sha256(cls, v: str) -> str:
        return _validate_sha256(v, "schema_sha256")  # type: ignore[return-value]


class MCPGatewayContract(BaseModel):
    """MCP Gateway 合約（ADR-113）"""

    model_config = {"extra": "forbid"}

    gateway_id: str
    gateway_name: str = Field(..., min_length=1, max_length=256)
    transport: Transport
    endpoint: str | None = None
    auth_scheme: AuthScheme = AuthScheme.NONE
    hmac_secret_ref: str | None = None
    tools_exposed: list[ToolExposed] = Field(default_factory=list)
    rate_limit_rpm: int | None = Field(None, ge=1)
    timeout_seconds: int = Field(30, ge=1, le=300)
    is_enabled: bool = True

    @model_validator(mode="after")
    def validate_http_endpoint(self) -> MCPGatewayContract:
        if self.transport in (Transport.HTTP, Transport.SSE) and not self.endpoint:
            raise ValueError(f"transport={self.transport} 時 endpoint 為必填")
        return self


# ─────────────────────────────────────────────────────────────────────────────
# 4. Policy Routing Contract
# ─────────────────────────────────────────────────────────────────────────────

class TimeRange(BaseModel):
    model_config = {"extra": "forbid"}

    start_utc: str = Field(..., pattern=r"^[0-2][0-9]:[0-5][0-9]$")
    end_utc: str = Field(..., pattern=r"^[0-2][0-9]:[0-5][0-9]$")


class RoutingCondition(BaseModel):
    model_config = {"extra": "forbid"}

    task_types: list[str] = Field(default_factory=list)
    max_prompt_tokens: int | None = Field(None, ge=1)
    time_range: TimeRange | None = None


class RoutingRule(BaseModel):
    model_config = {"extra": "forbid"}

    rule_id: str
    priority: int = Field(..., ge=0, le=9999)
    provider: Provider
    model: str
    condition: RoutingCondition | None = None
    weight: int = Field(100, ge=1, le=100)


class RetryPolicy(BaseModel):
    model_config = {"extra": "forbid"}

    max_retries: int = Field(3, ge=0, le=10)
    backoff_base_seconds: float = Field(1.0, ge=0.1, le=60)
    retry_on_provider_errors: bool = True


class PolicyRoutingContract(BaseModel):
    """路由/政策合約"""

    model_config = {"extra": "forbid"}

    policy_id: str
    policy_name: str = Field(..., min_length=1, max_length=256)
    routing_rules: list[RoutingRule] = Field(..., min_length=1)
    fallback_provider: Provider | None = None
    fallback_model: str | None = None
    max_cost_per_run_usd: float | None = Field(None, ge=0)
    retry_policy: RetryPolicy = Field(default_factory=RetryPolicy)
    effective_from: datetime | None = None
    effective_to: datetime | None = None


# ─────────────────────────────────────────────────────────────────────────────
# 5. Runtime Run State Contract
# ─────────────────────────────────────────────────────────────────────────────

class RunTrigger(BaseModel):
    model_config = {"extra": "forbid"}

    trigger_type: str = Field(
        ..., pattern="^(channel_event|schedule|api|sub_agent|retry)$"
    )
    channel_event_id: str | None = None
    schedule_id: str | None = None
    triggered_by: str | None = None


class RuntimeRunStateContract(BaseModel):
    """Run 狀態機合約（ADR-106 Phase 3）"""

    model_config = {"extra": "forbid"}

    run_id: str = Field(..., description="UUID v7")
    project_id: str
    agent_id: str
    state: RunState
    trace_id: str | None = None
    parent_run_id: str | None = None
    trigger: RunTrigger | None = None
    input_sha256: str | None = None
    output_sha256: str | None = None
    started_at: datetime | None = None
    completed_at: datetime | None = None
    timeout_at: datetime | None = None
    error_code: str | None = None
    cost_usd: float | None = Field(None, ge=0)
    step_count: int = Field(0, ge=0)

    @field_validator("run_id", "parent_run_id")
    @classmethod
    def validate_uuid(cls, v: str | None) -> str | None:
        if v is None:
            return v
        if not _UUID_RE.match(v):
            raise ValueError("必須為標準 UUID 格式")
        return v

    @field_validator("input_sha256", "output_sha256")
    @classmethod
    def validate_sha256_fields(cls, v: str | None) -> str | None:
        return _validate_sha256(v)

    @field_validator("project_id")
    @classmethod
    def validate_project_id(cls, v: str) -> str:
        if not _PROJECT_ID_RE.match(v):
            raise ValueError("project_id 格式不合法")
        return v


# ─────────────────────────────────────────────────────────────────────────────
# 6. Channel Event Contract
# ─────────────────────────────────────────────────────────────────────────────

class AttachmentRef(BaseModel):
    model_config = {"extra": "forbid"}

    attachment_type: str = Field(..., pattern="^(photo|document|audio|video)$")
    file_id: str
    sha256: str | None = None

    @field_validator("sha256")
    @classmethod
    def validate_sha256(cls, v: str | None) -> str | None:
        return _validate_sha256(v, "attachment sha256")


class ChannelEventContract(BaseModel):
    """Channel Event 合約（ADR-114 冪等去重）"""

    model_config = {"extra": "forbid"}

    event_id: str = Field(..., description="Platform 生成的 UUID")
    project_id: str
    channel_type: ChannelType
    event_type: EventType
    provider_event_id: str | None = Field(None, max_length=256)
    user_id: str | None = None
    chat_id: str | None = None
    payload: dict[str, Any] = Field(..., min_length=1)
    text: str | None = Field(None, max_length=4096)
    attachments: list[AttachmentRef] = Field(default_factory=list)
    run_id: str | None = None
    is_duplicate: bool = False
    received_at: datetime

    @field_validator("event_id", "run_id")
    @classmethod
    def validate_uuid(cls, v: str | None) -> str | None:
        if v is None:
            return v
        if not _UUID_RE.match(v):
            raise ValueError("必須為標準 UUID 格式")
        return v

    @field_validator("project_id")
    @classmethod
    def validate_project_id(cls, v: str) -> str:
        if not _PROJECT_ID_RE.match(v):
            raise ValueError("project_id 格式不合法")
        return v


# ─────────────────────────────────────────────────────────────────────────────
# Contract family dispatcher
# ─────────────────────────────────────────────────────────────────────────────

CONTRACT_FAMILY_MODELS: dict[str, type[BaseModel]] = {
    "project_tenant": ProjectTenantContract,
    "agent": AgentContract,
    "mcp_gateway": MCPGatewayContract,
    "policy_routing": PolicyRoutingContract,
    "runtime_run_state": RuntimeRunStateContract,
    "channel_event": ChannelEventContract,
}

VALID_CONTRACT_FAMILIES = frozenset(CONTRACT_FAMILY_MODELS.keys())


def validate_contract_body(family: str, body: dict[str, Any]) -> BaseModel:
    """
    依 contract_family 驗證 body_json。
    驗證失敗拋出 pydantic.ValidationError。
    """
    model_cls = CONTRACT_FAMILY_MODELS.get(family)
    if model_cls is None:
        raise ValueError(
            f"未知 contract_family: {family!r}。"
            f"合法值：{sorted(VALID_CONTRACT_FAMILIES)}"
        )
    return model_cls.model_validate(body)
