"""Pydantic models for wl-apdp SDK."""

from __future__ import annotations

from enum import Enum
from typing import Any

from pydantic import BaseModel, Field


class PrincipalType(str, Enum):
    """Types of principals supported by wl-apdp."""

    USER = "User"
    AGENT = "Agent"
    SERVICE = "Service"
    BOT = "Bot"


class Decision(str, Enum):
    """Authorization decision."""

    ALLOW = "Allow"
    DENY = "Deny"


class AuthorizationRequest(BaseModel):
    """Request for authorization decision.

    Attributes:
        principal: Cedar entity reference (e.g., 'Agent::"my-agent"').
        action: Cedar action reference (e.g., 'Action::"execute"').
        resource: Cedar resource reference (e.g., 'Tool::"web_search"').
        context: Additional context for policy evaluation.
    """

    principal: str
    action: str
    resource: str
    context: dict[str, Any] = Field(default_factory=dict)

    def model_dump_for_api(self) -> dict[str, Any]:
        """Dump model for API request, ensuring proper format."""
        return {
            "principal": self.principal,
            "action": self.action,
            "resource": self.resource,
            "context": self.context,
        }


class AuthorizationResponse(BaseModel):
    """Response from authorization decision.

    Attributes:
        decision: The authorization decision (Allow or Deny).
        diagnostics: Optional diagnostic information.
        determining_policies: List of policy IDs that determined the decision.
        errors: List of any errors encountered during evaluation.
    """

    decision: str
    diagnostics: dict[str, Any] | None = None
    determining_policies: list[str] = Field(default_factory=list)
    errors: list[str] = Field(default_factory=list)

    @property
    def is_allowed(self) -> bool:
        """Check if the decision is Allow."""
        return self.decision == Decision.ALLOW.value


class PolicyAnalysis(BaseModel):
    """Response from policy analysis.

    Attributes:
        applicable_policies: List of policy IDs that apply to the request.
        total_policies: Total number of policies in the system.
        analysis_time_ms: Time taken to analyze policies in milliseconds.
    """

    applicable_policies: list[str] = Field(default_factory=list)
    total_policies: int = 0
    analysis_time_ms: float = 0.0


class HealthResponse(BaseModel):
    """Health check response.

    Attributes:
        status: Health status (e.g., "healthy").
        version: Server version.
        service: Service name.
    """

    status: str
    version: str | None = None
    service: str | None = None


class Policy(BaseModel):
    """Cedar policy.

    Attributes:
        id: Unique policy identifier.
        name: Human-readable policy name.
        code: Cedar policy code.
        description: Optional policy description.
        active: Whether the policy is active.
    """

    id: str
    name: str
    code: str
    description: str | None = None
    active: bool = True


# Helper functions for building Cedar entity references


def cedar_principal(principal_type: PrincipalType | str, principal_id: str) -> str:
    """Build a Cedar principal reference.

    Args:
        principal_type: The type of principal (User, Agent, Service, Bot).
        principal_id: The unique identifier for the principal.

    Returns:
        Cedar entity reference string (e.g., 'Agent::"my-agent"').

    Example:
        >>> cedar_principal(PrincipalType.AGENT, "my-agent")
        'Agent::"my-agent"'
    """
    if isinstance(principal_type, PrincipalType):
        type_str = principal_type.value
    else:
        type_str = principal_type
    return f'{type_str}::"{principal_id}"'


def cedar_action(action: str) -> str:
    """Build a Cedar action reference.

    Args:
        action: The action name.

    Returns:
        Cedar action reference string (e.g., 'Action::"execute"').

    Example:
        >>> cedar_action("execute")
        'Action::"execute"'
    """
    return f'Action::"{action}"'


def cedar_resource(resource_type: str, resource_id: str) -> str:
    """Build a Cedar resource reference.

    Args:
        resource_type: The type of resource (e.g., "Tool", "Document").
        resource_id: The unique identifier for the resource.

    Returns:
        Cedar resource reference string (e.g., 'Tool::"web_search"').

    Example:
        >>> cedar_resource("Tool", "web_search")
        'Tool::"web_search"'
    """
    return f'{resource_type}::"{resource_id}"'


# =============================================================================
# Constraint Manifest Models (Agent-Native Constraint Interface)
# =============================================================================


class TrustState(str, Enum):
    """Trust state for agents and servers."""

    UNVERIFIED = "unverified"
    TRUSTED = "trusted"
    QUARANTINED = "quarantined"
    REVOKED = "revoked"


class RiskLevel(str, Enum):
    """Risk level for capabilities."""

    LOW = "low"
    MEDIUM = "medium"
    HIGH = "high"
    CRITICAL = "critical"


class AgentContext(BaseModel):
    """Agent context in a constraint manifest.

    Attributes:
        agent_id: Unique UUID identifier.
        slug: Friendly slug identifier (e.g., "researcher-agent").
        name: Display name.
        agent_type: Type of agent (autonomous, workflow, assistant).
        trust_level: Current trust level (0-10).
        trust_state: Current trust state.
        capabilities: List of agent capabilities.
    """

    agent_id: str
    slug: str = ""
    name: str = ""
    agent_type: str | None = None
    trust_level: int = 5
    trust_state: TrustState = TrustState.UNVERIFIED
    capabilities: list[str] = Field(default_factory=list)


class GoalContext(BaseModel):
    """Goal context in a constraint manifest.

    Attributes:
        goal_id: Unique goal identifier.
        objective: Goal description/objective.
        category: Goal category.
        status: Current goal status.
        action_count: Number of actions taken.
        max_actions: Maximum allowed actions.
        expires_at: Goal expiration time.
    """

    goal_id: str
    objective: str
    category: str
    status: str = "Active"
    action_count: int = 0
    max_actions: int = 100
    expires_at: str | None = None


class ResourcePattern(BaseModel):
    """Pattern for matching resources.

    Attributes:
        pattern_type: Type of pattern (prefix, suffix, regex, exact).
        value: Pattern value.
    """

    pattern_type: str
    value: str


class Capability(BaseModel):
    """A capability that describes what an agent can do.

    Attributes:
        action: The action this capability allows.
        resource_types: Types of resources this applies to.
        resource_patterns: Patterns for matching specific resources.
        requires_intent: Intent categories required to use this capability.
        recommend_preflight: Whether to recommend pre-flight check.
        risk_level: Risk level of this capability.
        description: Human-readable description.
    """

    action: str
    resource_types: list[str] = Field(default_factory=list)
    resource_patterns: list[ResourcePattern] = Field(default_factory=list)
    requires_intent: list[str] = Field(default_factory=list)
    recommend_preflight: bool = False
    risk_level: RiskLevel = RiskLevel.LOW
    description: str = ""


class ForbiddenAction(BaseModel):
    """An action that is explicitly forbidden.

    Attributes:
        action: The forbidden action.
        resource_type: Optional resource type this applies to.
        reason: Reason why this action is forbidden.
    """

    action: str
    resource_type: str | None = None
    reason: str


class RequiredContext(BaseModel):
    """Required context field for authorization.

    Attributes:
        field: Name of the required field.
        description: Description of the field.
        required: Whether the field is required (default True).
    """

    field: str
    description: str | None = None
    required: bool = True


class ConstraintSet(BaseModel):
    """Set of constraints for an agent.

    Attributes:
        forbidden_actions: List of explicitly forbidden actions.
        approval_required: Intent categories that require approval.
        required_context: Context fields that must be present.
        max_concurrent_actions: Maximum concurrent actions allowed.
    """

    forbidden_actions: list[ForbiddenAction] = Field(default_factory=list)
    approval_required: list[str] = Field(default_factory=list)
    required_context: list[RequiredContext] = Field(default_factory=list)
    max_concurrent_actions: int | None = None


class EscalationRule(BaseModel):
    """Rule for when and how to escalate.

    Attributes:
        condition: What condition triggers this escalation.
        escalation_type: Type of escalation (human_approval, supervisor_agent, queue).
        target: Target for escalation (e.g., user ID, agent ID, queue name).
        priority: Priority level (low, medium, high, critical).
        description: Human-readable description.
    """

    condition: str
    escalation_type: str
    target: str | None = None
    priority: str | None = None
    description: str | None = None


class IntentActionMapping(BaseModel):
    """Mapping from intent category to allowed actions/resources.

    Attributes:
        intent_category: The intent category.
        allowed_actions: Actions allowed under this intent.
        allowed_resource_types: Resource types allowed.
        max_actions_per_session: Maximum actions per session for this intent.
    """

    intent_category: str
    allowed_actions: list[str] = Field(default_factory=list)
    allowed_resource_types: list[str] = Field(default_factory=list)
    max_actions_per_session: int | None = None


class DynamicLimits(BaseModel):
    """Dynamic limits that change based on usage.

    Attributes:
        actions_remaining: Actions remaining in current goal.
        seconds_until_expiry: Time until expiration (in seconds).
        rate_limit_remaining: API rate limit remaining.
        token_budget_remaining: Token budget remaining.
    """

    actions_remaining: int | None = None
    seconds_until_expiry: int | None = None
    rate_limit_remaining: int | None = None
    token_budget_remaining: int | None = None


class AgentGuidance(BaseModel):
    """Natural language guidance for agents.

    Attributes:
        role_summary: Summary of the agent's role and purpose.
        primary_objectives: List of primary objectives.
        limitations: List of limitations to be aware of.
        best_practices: Best practices for this agent.
        escalation_guidance: Guidance on when to escalate.
    """

    role_summary: str = ""
    primary_objectives: list[str] = Field(default_factory=list)
    limitations: list[str] = Field(default_factory=list)
    best_practices: list[str] = Field(default_factory=list)
    escalation_guidance: str = ""


class EffectiveScope(BaseModel):
    """Effective scope from delegation chain.

    Attributes:
        allowed_actions: Actions allowed by delegation.
        allowed_resource_types: Resource types allowed.
        allowed_intent_categories: Intent categories allowed.
        max_trust_level: Maximum trust level from delegation.
    """

    allowed_actions: list[str] = Field(default_factory=list)
    allowed_resource_types: list[str] = Field(default_factory=list)
    allowed_intent_categories: list[str] = Field(default_factory=list)
    max_trust_level: int | None = None


class ConstraintManifest(BaseModel):
    """Complete constraint manifest for an agent session.

    The constraint manifest provides agents with a complete picture of their
    operational boundaries at session start. Agents can use this to:
    - Self-govern against cached constraints
    - Pre-flight check uncertain actions
    - Know escalation paths when blocked

    Attributes:
        version: Manifest version.
        manifest_id: Unique manifest identifier.
        generated_at: When the manifest was generated.
        expires_at: When the manifest expires.
        agent: Agent context.
        active_goal: Currently active goal, if any.
        delegation_scope: Effective scope from delegation chain.
        capabilities: List of capabilities the agent has.
        constraints: Constraints the agent must follow.
        escalation_rules: Rules for escalation.
        intent_action_map: Mapping from intents to actions.
        dynamic_limits: Current dynamic limits.
        guidance: Natural language guidance.
    """

    version: str = "1.0"
    manifest_id: str
    generated_at: str
    expires_at: str

    agent: AgentContext
    active_goal: GoalContext | None = None
    delegation_scope: EffectiveScope | None = None

    capabilities: list[Capability] = Field(default_factory=list)
    constraints: ConstraintSet = Field(default_factory=ConstraintSet)
    escalation_rules: list[EscalationRule] = Field(default_factory=list)
    intent_action_map: dict[str, IntentActionMapping] = Field(default_factory=dict)
    dynamic_limits: DynamicLimits = Field(default_factory=DynamicLimits)
    guidance: AgentGuidance = Field(default_factory=AgentGuidance)

    def can_perform(self, action: str, resource_type: str | None = None) -> bool:
        """Quick check if an action is potentially allowed.

        This is a local check against cached manifest - not authoritative.
        Use preflight() for server-side validation.

        Args:
            action: The action to check.
            resource_type: Optional resource type to check.

        Returns:
            True if the action appears to be allowed, False if forbidden.
        """
        # Check forbidden actions
        for forbidden in self.constraints.forbidden_actions:
            if forbidden.action == action:
                if resource_type is None or forbidden.resource_type is None:
                    return False
                if forbidden.resource_type == resource_type:
                    return False

        # Check if any capability allows this action
        for cap in self.capabilities:
            if cap.action == action:
                if resource_type is None:
                    return True
                if resource_type in cap.resource_types:
                    return True

        return False

    def get_required_intent(self, action: str) -> list[str]:
        """Get intent categories required for an action.

        Args:
            action: The action to check.

        Returns:
            List of required intent categories.
        """
        for cap in self.capabilities:
            if cap.action == action:
                return cap.requires_intent
        return []

    def should_preflight(self, action: str) -> bool:
        """Check if pre-flight is recommended for an action.

        Args:
            action: The action to check.

        Returns:
            True if pre-flight check is recommended.
        """
        for cap in self.capabilities:
            if cap.action == action:
                return cap.recommend_preflight
        return True  # Default to recommending preflight for unknown actions


class IntentDeclaration(BaseModel):
    """Intent declaration for authorization.

    Attributes:
        objective: Human-readable description of the objective (required).
        category: Intent category (lowercase: research, data_analysis, etc.).
        risk_level: Risk level assessment (low, medium, high, critical).
        justification: Justification for why this action is needed.
        parent_intent_id: Parent intent ID for delegation chains.
    """

    objective: str
    category: str
    risk_level: str = "low"
    justification: str | None = None
    parent_intent_id: str | None = None


class PreflightRequest(BaseModel):
    """Request for pre-flight authorization check.

    Attributes:
        agent_id: Agent ID making the request.
        action: Action to check.
        resource: Resource to check.
        resource_type: Optional resource type for type-specific checks.
        intent: Intent declaration (optional).
        goal_id: Goal ID (optional).
        context: Additional context.
    """

    agent_id: str
    action: str
    resource: str
    resource_type: str | None = None
    intent: IntentDeclaration | None = None
    goal_id: str | None = None
    context: dict[str, Any] = Field(default_factory=dict)


class PreflightIssue(BaseModel):
    """An issue found during pre-flight check.

    Attributes:
        code: Issue code.
        message: Human-readable message.
        severity: Issue severity (warning, error).
        field: Field that caused the issue.
    """

    code: str
    message: str
    severity: str = "error"
    field: str | None = None


class Suggestion(BaseModel):
    """A suggestion for resolving a pre-flight issue.

    Attributes:
        action: Suggested action to take.
        description: Description of the suggestion.
        alternative: Alternative resource or action, if applicable.
    """

    action: str
    description: str
    alternative: str | None = None


class EscalationDetails(BaseModel):
    """Details about required escalation.

    Attributes:
        reason: Why escalation is required.
        escalation_type: Type of escalation needed.
        approver: Who should approve.
        estimated_wait: Estimated wait time.
    """

    reason: str
    escalation_type: str
    approver: str | None = None
    estimated_wait: str | None = None


class PreflightResponse(BaseModel):
    """Response from pre-flight authorization check.

    Attributes:
        allowed: Whether the action would be allowed.
        reason_code: Code explaining the decision.
        explanation: Human-readable explanation.
        issues: List of issues found.
        suggestions: List of suggestions.
        escalation_required: Escalation details if needed.
        remaining_actions: Remaining actions if limits apply.
    """

    allowed: bool
    reason_code: str
    explanation: str
    issues: list[PreflightIssue] = Field(default_factory=list)
    suggestions: list[Suggestion] = Field(default_factory=list)
    escalation_required: EscalationDetails | None = None
    remaining_actions: int | None = None


class CapabilitiesQuery(BaseModel):
    """Query for available capabilities.

    Attributes:
        agent_id: Agent to query capabilities for.
        resource_type: Filter by resource type.
        action: Filter by action.
        intent_category: Filter by intent category.
    """

    agent_id: str
    resource_type: str | None = None
    action: str | None = None
    intent_category: str | None = None


class CapabilitiesResponse(BaseModel):
    """Response from capabilities query.

    Attributes:
        capabilities: List of matching capabilities.
        total: Total number of capabilities.
    """

    capabilities: list[Capability] = Field(default_factory=list)
    total: int = 0
