"""Alert receipts built on top of the governance/observability substrate."""

from __future__ import annotations

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

from api.hashing import hash_content
from api.ulid import generate_ulid
from alerts.models import (
    AlertSeverity,
    AlertCategory,
    AlertRecipient,
    DeliveryChannel,
    SuggestedAction,
    AutoRemediation,
    TraceContext,
    AlertMetadata,
)
from receipts.domains.governance import GovernanceReceipt
from receipts.compat.pyd import Field, field_validator, model_validator

if TYPE_CHECKING:  # pragma: no cover - type checking only
    from alerts.models import Alert


class AlertReceipt(GovernanceReceipt):
    """Receipt emitted whenever the alerting system escalates an incident."""

    receipt_type: Literal["AlertReceipt"] = Field(default="AlertReceipt")
    domain: str = Field(default="observability")
    organ: str = Field(default="alert_router")

    profile: str = Field(
        default="observability",
        description="Profile determining signing/retention behavior for alerts",
    )
    receipt_mode: str = Field(
        default="signed",
        description="Receipt mode: 'signed' for full attestation or 'minimal'",
    )
    retention_days: Optional[int] = Field(default=90, description="Retention period in days")
    encryption_required: bool = Field(default=True, description="Whether encryption is required")

    severity: AlertSeverity = Field(description="Alert severity tier")
    category: AlertCategory = Field(description="Alert routing category")
    message: str = Field(..., min_length=1, max_length=2000)

    recipients: List[AlertRecipient] = Field(..., min_length=1)
    specific_targets: List[str] = Field(default_factory=list)

    machine_context: Dict[str, Any] = Field(default_factory=dict)
    suggested_actions: List[SuggestedAction] = Field(default_factory=list)
    auto_remediation: Optional[AutoRemediation] = Field(default=None)

    requires_ack: bool = Field(default=False)
    ack_deadline: Optional[datetime] = Field(default=None)

    triggered_by: Optional[str] = Field(default=None)
    related_alerts: List[str] = Field(default_factory=list)

    trace_context: Optional[TraceContext] = Field(default=None)
    metadata: Optional[AlertMetadata] = Field(default=None)

    channels: List[DeliveryChannel] = Field(default_factory=list)
    priority: int = Field(default=5, ge=0, le=10)
    dedup_key: Optional[str] = Field(default=None)

    expires_at: Optional[datetime] = Field(default=None)
    alert_hash: Optional[str] = Field(default=None, description="Hash of canonical alert payload")

    receipt_id: str = Field(default_factory=lambda: f"alert-{generate_ulid()}")

    @field_validator("recipients")
    @classmethod
    def _ensure_recipients(cls, recipients: List[AlertRecipient]) -> List[AlertRecipient]:
        if not recipients:
            raise ValueError("AlertReceipt must target at least one recipient")
        # Deduplicate while preserving order
        seen: set[AlertRecipient] = set()
        ordered: List[AlertRecipient] = []
        for recipient in recipients:
            if recipient not in seen:
                ordered.append(recipient)
                seen.add(recipient)
        return ordered

    @model_validator(mode="before")
    @classmethod
    def _populate_defaults(cls, data: Any) -> Any:
        if not isinstance(data, dict):
            return data
        payload = dict(data)

        # Default ack deadline for acknowledged alerts
        requires_ack = payload.get("requires_ack")
        ack_deadline = payload.get("ack_deadline")
        severity = payload.get("severity")
        if requires_ack and not ack_deadline and severity is not None:
            severity_value = getattr(severity, "numeric_value", 0)
            hours = 1 if severity_value >= 6 else 4
            ts = payload.get("ts")
            ts_value = ts if isinstance(ts, datetime) else datetime.now(timezone.utc)
            payload["ack_deadline"] = ts_value + timedelta(hours=hours)

        # Automatically compute alert hash when absent
        if not payload.get("alert_hash"):
            canonical = {
                "severity": getattr(severity, "value", severity),
                "category": getattr(payload.get("category"), "value", payload.get("category")),
                "message": payload.get("message"),
                "machine_context": payload.get("machine_context", {}),
                "triggered_by": payload.get("triggered_by"),
            }
            payload["alert_hash"] = hash_content(canonical)

        return payload

    def to_dict(self) -> Dict[str, Any]:
        data = super().to_dict()
        data["profile"] = self.profile
        data["receipt_mode"] = self.receipt_mode
        data["retention_days"] = self.retention_days
        data["encryption_required"] = self.encryption_required
        data["alert_hash"] = self.alert_hash
        return data

    @classmethod
    def from_alert(cls, alert: "Alert", **overrides: Any) -> "AlertReceipt":
        """Build an alert receipt from an Alert domain object."""
        ts = alert.created_at
        if ts.tzinfo is None:
            ts = ts.replace(tzinfo=timezone.utc)
        else:
            ts = ts.astimezone(timezone.utc)

        base_payload: Dict[str, Any] = {
            "receipt_id": overrides.pop("receipt_id", alert.receipt_id or f"alert-{generate_ulid()}"),
            "severity": alert.severity,
            "category": alert.category,
            "message": alert.message,
            "recipients": list(alert.recipients),
            "specific_targets": list(alert.specific_targets),
            "machine_context": dict(alert.machine_context),
            "suggested_actions": list(alert.suggested_actions),
            "auto_remediation": alert.auto_remediation,
            "requires_ack": alert.requires_ack,
            "ack_deadline": alert.ack_deadline,
            "triggered_by": alert.triggered_by,
            "related_alerts": list(alert.related_alerts),
            "trace_context": alert.trace_context,
            "metadata": alert.metadata,
            "channels": list(alert.channels),
            "priority": alert.priority,
            "dedup_key": alert.dedup_key or alert.compute_dedup_key(),
            "expires_at": alert.expires_at,
            "alert_hash": getattr(alert, "alert_hash", None),
            "ts": ts,
            "tenant": overrides.pop("tenant", None),
        }

        base_payload.update(overrides)
        return cls(**base_payload)


__all__ = ["AlertReceipt"]
