"""Policy schema — Pydantic models for agentsh policy definitions.

See spec Section 7.1 for the full model hierarchy.
"""

from __future__ import annotations

from typing import Any, Literal

from pydantic import BaseModel, ConfigDict, field_validator

FileOp = Literal["read", "write", "create", "delete"]

# All policy models use extra="forbid" to catch typos in policy keys.

# ─── File rules ─────────────────────────────────────────


class FileAllowRule(BaseModel):
    model_config = ConfigDict(extra="forbid")
    allow: str | list[str]
    ops: list[FileOp] | None = None


class FileDenyRule(BaseModel):
    model_config = ConfigDict(extra="forbid")
    deny: str | list[str]
    ops: list[FileOp] | None = None


class FileRedirectRule(BaseModel):
    model_config = ConfigDict(extra="forbid")
    redirect: str | list[str]
    to: str
    ops: list[FileOp] | None = None


class FileAuditRule(BaseModel):
    model_config = ConfigDict(extra="forbid")
    audit: str | list[str]
    ops: list[FileOp] | None = None


class FileSoftDeleteRule(BaseModel):
    model_config = ConfigDict(extra="forbid")
    soft_delete: str | list[str]


FileRule = FileAllowRule | FileDenyRule | FileRedirectRule | FileAuditRule | FileSoftDeleteRule

# ─── Network rules ──────────────────────────────────────


class NetworkAllowRule(BaseModel):
    model_config = ConfigDict(extra="forbid")
    allow: str | list[str]
    ports: list[int] | None = None

    @field_validator("ports", mode="before")
    @classmethod
    def _validate_ports(cls, v: list[int] | None) -> list[int] | None:
        if v is not None:
            for p in v:
                if not isinstance(p, int):
                    raise ValueError(f"Port must be an integer, got {type(p).__name__}: {p}")
                if not (1 <= p <= 65535):
                    raise ValueError(f"Port {p} out of range 1-65535")
        return v


class NetworkDenyRule(BaseModel):
    model_config = ConfigDict(extra="forbid")
    deny: str | list[str]


class NetworkRedirectRule(BaseModel):
    model_config = ConfigDict(extra="forbid")
    redirect: str
    to: str


NetworkRule = NetworkAllowRule | NetworkDenyRule | NetworkRedirectRule

# ─── Command rules ──────────────────────────────────────


class CommandRedirectTarget(BaseModel):
    model_config = ConfigDict(extra="forbid")
    cmd: str
    args: list[str]


class CommandAllowRule(BaseModel):
    model_config = ConfigDict(extra="forbid")
    allow: str | list[str]


class CommandDenyRule(BaseModel):
    model_config = ConfigDict(extra="forbid")
    deny: str | list[str]


class CommandRedirectRule(BaseModel):
    model_config = ConfigDict(extra="forbid")
    redirect: str | list[str]
    to: str | CommandRedirectTarget


CommandRule = CommandAllowRule | CommandDenyRule | CommandRedirectRule

# ─── Env rules ──────────────────────────────────────────


class EnvRule(BaseModel):
    model_config = ConfigDict(extra="forbid")
    commands: list[str]
    allow: list[str] | None = None
    deny: list[str] | None = None


# ─── DNS / Connect redirects ────────────────────────────


class DnsRedirect(BaseModel):
    model_config = ConfigDict(extra="forbid")
    match: str
    resolve_to: str


class ConnectRedirect(BaseModel):
    model_config = ConfigDict(extra="forbid")
    match: str
    redirect_to: str


# ─── PolicyDefinition ───────────────────────────────────


class PolicyDefinition(BaseModel):
    model_config = ConfigDict(extra="forbid")
    file: list[FileRule] | None = None
    network: list[NetworkRule] | None = None
    commands: list[CommandRule] | None = None
    env: list[EnvRule] | None = None
    dns: list[DnsRedirect] | None = None
    connect: list[ConnectRedirect] | None = None


def validate_policy(policy: dict[str, Any] | PolicyDefinition) -> PolicyDefinition:
    """Validate and return a PolicyDefinition. Raises ValidationError."""
    if isinstance(policy, PolicyDefinition):
        return policy
    return PolicyDefinition.model_validate(policy)
