"""
Structured Rejection Envelope — {received, reason, allowed, hint}.

Every blocked action returns a standard 4-field envelope so the verifier is
self-documenting: the error message IS the documentation.

Inspired by pi-agents's blocked-action format, ported to be Agno-agnostic.
"""

from __future__ import annotations

import json
from dataclasses import dataclass, field, asdict
from datetime import datetime, timezone
from typing import Any, Optional


@dataclass
class RejectionEnvelope:
    """Universal envelope for blocked actions.

    Fields:
      received: What the agent tried to do (verbatim action or intent)
      reason:   Why it was blocked (machine-readable short code + human explanation)
      allowed:  What the agent CAN do instead (alternative action, or None if completely denied)
      hint:     How to resolve the rejection (docs reference, fix instruction, or None)
    """

    received: str
    reason: str
    allowed: Optional[str] = None
    hint: Optional[str] = None
    timestamp: str = field(default_factory=lambda: datetime.now(timezone.utc).isoformat())

    def dict(self) -> dict[str, Any]:
        return {
            "received": self.received,
            "reason": self.reason,
            "allowed": self.allowed,
            "hint": self.hint,
            "timestamp": self.timestamp,
        }

    def to_json(self, indent: int = 2) -> str:
        return json.dumps(self.dict(), indent=indent)

    def __str__(self) -> str:
        parts = [f"BLOCKED: {self.reason}"]
        if self.allowed:
            parts.append(f"  Allowed: {self.allowed}")
        if self.hint:
            parts.append(f"  Hint: {self.hint}")
        return "\n".join(parts)

    @classmethod
    def from_json(cls, raw: str) -> RejectionEnvelope:
        data = json.loads(raw)
        return cls(
            received=data["received"],
            reason=data["reason"],
            allowed=data.get("allowed"),
            hint=data.get("hint"),
            timestamp=data.get("timestamp", ""),
        )

    @classmethod
    def block(
        cls,
        received: str,
        reason: str,
        allowed: Optional[str] = None,
        hint: Optional[str] = None,
    ) -> RejectionEnvelope:
        """Convenience constructor for a blocking rejection."""
        return cls(received=received, reason=reason, allowed=allowed, hint=hint)


# ── Blocker: a rule that generates RejectionEnvelopes ──────────────────────────


@dataclass
class Blocker:
    """A named rule that evaluates an action and returns a rejection or None.

    Usage::

        no_write_file = Blocker(
            name="no_direct_write",
            match=lambda action: "write_file" in action.get("tool", ""),
            reject=lambda action: RejectionEnvelope.block(
                received=str(action),
                reason="LEADER_MUST_NOT_WRITE_FILES",
                allowed="Use delegate_task to spawn a writer subagent",
                hint="Leaders read, delegate, and synthesize — they never write files directly.",
            ),
        )
    """

    name: str
    match: Any  # Callable[[dict], bool]
    reject: Any  # Callable[[dict], RejectionEnvelope]

    def evaluate(self, action: dict) -> Optional[RejectionEnvelope]:
        """Return a RejectionEnvelope if this action is blocked, None otherwise."""
        if self.match(action):
            return self.reject(action)
        return None
