"""Automation Guard models for CleverAgents v3.

An **AutomationGuard** provides runtime enforcement hooks that gate tool
invocations beyond the phase-transition thresholds defined by an
:class:`AutomationProfile`.  Guards support:

- **Max tool calls** — Limit the number of tool invocations per step.
- **Budget cap** — Halt automatic execution when cumulative cost
  exceeds a threshold.
- **Allowlist / Denylist** — Control which tools may proceed
  automatically.
- **Write approval** — Require human approval for write operations.
- **Apply approval** — Require human approval before the apply phase.

A **GuardResult** captures the outcome of a guard evaluation:
``allowed``, ``reason``, and ``requires_approval``.

Based on ``docs/specification.md`` Section "Automation Profiles"
(enforcement hooks).
"""

from __future__ import annotations

from enum import StrEnum

from pydantic import BaseModel, ConfigDict, Field, field_validator

# ---------------------------------------------------------------------------
# GuardScope
# ---------------------------------------------------------------------------


class GuardScope(StrEnum):
    """Scope at which a guard check is evaluated.

    Used by :meth:`AutomationProfile.check_guard` to distinguish
    plan-level from subplan-level enforcement.
    """

    PLAN = "plan"
    SUBPLAN = "subplan"


# ---------------------------------------------------------------------------
# Remediation guidance constants
# ---------------------------------------------------------------------------

REMEDIATION_DENYLIST: str = (
    "Remediation: remove it from the denylist or request human "
    "approval for this operation."
)
REMEDIATION_ALLOWLIST: str = (
    "Remediation: add it to the allowlist or request human approval."
)
REMEDIATION_TOOL_CALL_LIMIT: str = (
    "Remediation: increase max_tool_calls_per_step, reduce tool "
    "usage, or request human approval."
)
REMEDIATION_BUDGET: str = (
    "Remediation: raise max_total_cost, lower cost, or request human approval."
)
REMEDIATION_WRITE_APPROVAL: str = (
    "Remediation: request human approval before continuing."
)
REMEDIATION_APPLY_APPROVAL: str = (
    "Remediation: request human approval to proceed with apply."
)

# ---------------------------------------------------------------------------
# GuardResult
# ---------------------------------------------------------------------------


class GuardResult(BaseModel):
    """Result of evaluating an automation guard check.

    Returned by :meth:`AutomationProfile.check_guard` to indicate
    whether a tool invocation is allowed, blocked, or requires human
    approval.
    """

    allowed: bool = Field(
        ...,
        description="Whether the tool invocation is allowed to proceed",
    )
    reason: str | None = Field(
        default=None,
        description="Human-readable reason when not allowed",
    )
    requires_approval: bool = Field(
        default=False,
        description=("Whether human approval is required before proceeding"),
    )

    model_config = ConfigDict(
        str_strip_whitespace=True,
    )


# ---------------------------------------------------------------------------
# AutomationGuard
# ---------------------------------------------------------------------------


class AutomationGuard(BaseModel):
    """Enforcement hooks for an automation profile.

    Guards provide additional runtime constraints beyond the phase-
    transition thresholds.  They gate tool invocations based on call
    counts, budgets, allowlists/denylists, and write/apply semantics.
    """

    max_tool_calls_per_step: int | None = Field(
        default=None,
        description=(
            "Maximum tool invocations per step before requiring "
            "approval.  None means unlimited."
        ),
    )
    max_total_cost: float | None = Field(
        default=None,
        description=(
            "Budget cap (cumulative cost) before requiring approval.  "
            "None means unlimited."
        ),
    )
    tool_allowlist: list[str] | None = Field(
        default=None,
        description=(
            "Only these tools may be called automatically.  "
            "None means all tools are allowed."
        ),
    )
    tool_denylist: list[str] | None = Field(
        default=None,
        description=(
            "These tools always require human approval.  "
            "None means no tools are denied."
        ),
    )
    require_approval_for_writes: bool = Field(
        default=False,
        description="Require human approval for write operations",
    )
    require_approval_for_apply: bool = Field(
        default=False,
        description="Require human approval before the apply phase",
    )

    @field_validator("max_tool_calls_per_step")
    @classmethod
    def validate_max_tool_calls(
        cls: type[AutomationGuard],
        v: int | None,
    ) -> int | None:
        """Validate max_tool_calls_per_step is positive if set."""
        if v is not None and v < 0:
            raise ValueError(f"max_tool_calls_per_step must be >= 0, got {v}")
        return v

    @field_validator("max_total_cost")
    @classmethod
    def validate_max_total_cost(
        cls: type[AutomationGuard],
        v: float | None,
    ) -> float | None:
        """Validate max_total_cost is non-negative if set."""
        if v is not None and v < 0.0:
            raise ValueError(f"max_total_cost must be >= 0.0, got {v}")
        return v

    model_config = ConfigDict(
        str_strip_whitespace=True,
        validate_assignment=True,
    )
