"""
DignityBudgetRefusalReceipt: Receipt when action is blocked due to dignity budget.

Implements Claim 14 (dignity floor enforcement):
- Tracks when cumulative dignity impact would exceed budget
- Records the budget state before/after
- Provides remediation guidance

The dignity budget tracks cumulative dignity impact over a time window,
preventing death-by-a-thousand-cuts scenarios where many small dignity
violations accumulate.
"""
from __future__ import annotations

from datetime import datetime, timezone
from typing import Any, Dict, List, Literal, Optional

from receipts.compat.pyd import Field, model_validator
from receipts.base import BaseReceipt, Domain


class DignityBudgetRefusalReceipt(BaseReceipt):
    """
    Receipt documenting a refusal due to dignity budget exhaustion.

    When an action would push cumulative dignity impact below the floor,
    even if the individual action's dignity score is above the floor,
    this receipt is emitted to document the refusal.
    """

    receipt_type: str = Field(default="DignityBudgetRefusalReceipt")
    domain: str = Field(default=Domain.GOVERNANCE.value)

    # Action that was refused
    action: str = Field(
        ...,
        description="Action that was refused",
    )
    action_target: Optional[str] = Field(
        default=None,
        description="Target of the action",
    )
    agent_id: str = Field(
        ...,
        description="Agent whose action was refused",
    )

    # Individual action dignity
    action_dignity_score: float = Field(
        ...,
        ge=0.0,
        le=1.0,
        description="Dignity score of this specific action",
    )
    action_dignity_delta: float = Field(
        ...,
        description="Projected dignity change from this action (negative = harm)",
    )

    # Budget state (Claim 14)
    budget_window_hours: int = Field(
        default=24,
        ge=1,
        description="Time window for budget calculation",
    )
    budget_floor: float = Field(
        default=0.15,
        ge=0.0,
        le=1.0,
        description="Minimum cumulative dignity threshold",
    )
    budget_before: float = Field(
        ...,
        ge=0.0,
        le=1.0,
        description="Cumulative dignity before this action",
    )
    budget_projected: float = Field(
        ...,
        description="What budget would be after action (can be negative)",
    )
    budget_deficit: float = Field(
        ...,
        ge=0.0,
        description="How far below floor the projected budget would be",
    )

    # Refusal details
    refusal_reason: str = Field(
        default="dignity_budget_exhausted",
        description="Reason code for refusal",
    )
    refusal_explanation: str = Field(
        ...,
        description="Human-readable explanation of why action was refused",
    )

    # Context
    recent_dignity_impacts: List[Dict[str, Any]] = Field(
        default_factory=list,
        description="Recent actions that consumed dignity budget",
    )
    time_until_budget_recovery: Optional[int] = Field(
        default=None,
        ge=0,
        description="Minutes until budget recovers enough to allow action",
    )

    # Remediation
    remediation_options: List[str] = Field(
        default_factory=list,
        description="Suggested ways to recover dignity budget",
    )
    escalation_available: bool = Field(
        default=True,
        description="Whether human escalation can override",
    )

    @model_validator(mode="after")
    def _validate_budget_math(self) -> "DignityBudgetRefusalReceipt":
        """Validate budget calculations are consistent."""
        expected_projected = self.budget_before + self.action_dignity_delta
        # Allow small floating point differences
        if abs(self.budget_projected - expected_projected) > 0.001:
            raise ValueError(
                f"budget_projected ({self.budget_projected}) should be "
                f"budget_before ({self.budget_before}) + action_dignity_delta ({self.action_dignity_delta})"
            )
        if self.budget_projected >= self.budget_floor:
            raise ValueError(
                "DignityBudgetRefusalReceipt should only be created when "
                "budget_projected < budget_floor"
            )
        expected_deficit = self.budget_floor - self.budget_projected
        if abs(self.budget_deficit - expected_deficit) > 0.001:
            raise ValueError(
                f"budget_deficit ({self.budget_deficit}) should be "
                f"budget_floor ({self.budget_floor}) - budget_projected ({self.budget_projected})"
            )
        return self


def create_dignity_budget_refusal(
    action: str,
    agent_id: str,
    action_dignity_delta: float,
    budget_before: float,
    budget_floor: float = 0.15,
    **kwargs,
) -> DignityBudgetRefusalReceipt:
    """Factory function to create a DignityBudgetRefusalReceipt."""
    import uuid
    budget_projected = budget_before + action_dignity_delta
    budget_deficit = budget_floor - budget_projected

    return DignityBudgetRefusalReceipt(
        receipt_id=f"dbr_{uuid.uuid4().hex[:16]}",
        action=action,
        agent_id=agent_id,
        action_dignity_score=max(0.0, min(1.0, 0.5 + action_dignity_delta)),  # Approximate
        action_dignity_delta=action_dignity_delta,
        budget_before=budget_before,
        budget_projected=budget_projected,
        budget_deficit=budget_deficit,
        budget_floor=budget_floor,
        refusal_explanation=(
            f"Action '{action}' would reduce dignity budget from {budget_before:.2f} "
            f"to {budget_projected:.2f}, which is {budget_deficit:.2f} below the "
            f"floor of {budget_floor:.2f}"
        ),
        **kwargs,
    )


__all__ = ["DignityBudgetRefusalReceipt", "create_dignity_budget_refusal"]
