"""
Pydantic models for CHOIR Merge Receipt v2.

Matches schemas/receipt.merge.v2.json with production hardening:
- χ-score computed at query time (not stored)
- proof_refs instead of full proof payloads
- CloudEvents compatible tri-temporal semantics
- Tier inheritance from parents
"""

from datetime import datetime, timezone
from typing import List, Optional, Dict, Any, Literal
from pydantic import BaseModel, ConfigDict, Field, field_validator, computed_field
import ulid


class ParentReceipt(BaseModel):
    """Reference to parent receipt in merge."""
    receipt_id: str = Field(..., pattern=r"^[a-z]+-[0-9A-HJKMNP-TV-Z]{26}$")
    digest: str = Field(..., pattern=r"^(sha256|blake3):[a-f0-9]{64}$")
    agent_id: str = Field(..., pattern=r"^agent-[a-z0-9_-]+$")
    timestamp: Optional[datetime] = None


class MergeStrategy(BaseModel):
    """Merge strategy configuration."""
    type: Literal["union", "reconcile", "weighted", "guardian"]
    weights: Optional[Dict[str, float]] = None
    guardian_config: Optional[Dict[str, Any]] = None


class Conflict(BaseModel):
    """Conflict detected and resolved during merge."""
    path: str = Field(..., description="JSONPath to conflicting field")
    before: List[Any] = Field(..., description="Conflicting values from parents")
    after: Any = Field(..., description="Resolved value")
    resolution: Literal["guardian", "weighted", "max", "min", "median", "union", "manual"]
    confidence: Optional[float] = Field(None, ge=0.0, le=1.0)


class Digest(BaseModel):
    """Cryptographic digest."""
    algorithm: Literal["sha256", "blake3"] = "sha256"
    parents_hash: Optional[str] = Field(None, pattern=r"^(sha256|blake3):[a-f0-9]{64}$")
    merge_result_hash: Optional[str] = Field(None, pattern=r"^(sha256|blake3):[a-f0-9]{64}$")


class ProofRefs(BaseModel):
    """
    Lightweight proof references (heavy proofs stored in separate tables/files).

    Production hardening:
    - TSA tokens stored in proofs.rfc3161/ with URI reference
    - Rekor inclusion proofs cached in proofs.rekor/
    - Batch Merkle trees in proofs.batches/
    """
    jcs_hash: str = Field(..., pattern=r"^(sha256|blake3):[a-f0-9]{64}$", description="JCS canonical hash (RFC 8785)")
    batch_id: Optional[str] = Field(None, pattern=r"^batch-[0-9A-HJKMNP-TV-Z]{26}$")
    batch_root: Optional[str] = Field(None, pattern=r"^(sha256|blake3):[a-f0-9]{64}$")
    tsa_ref: Optional[str] = Field(None, description="RFC 3161 TSA token URI (e.g., rfc3161://tsa.example/token/01M...)")
    rekor_uuid: Optional[str] = Field(None, pattern=r"^[a-f0-9]{64}$")
    rekor_log_index: Optional[int] = Field(None, ge=0)
    proof_level: Optional[Literal["hash", "signed", "timestamped", "transparent"]] = None


class AntiGamingSignals(BaseModel):
    """
    Composite anti-gaming signals (Ωc) for detecting coordination attacks.

    Production hardening: Detects market manipulation, ensemble gaming,
    burst attacks, out-of-distribution inputs, and low-trust sources.
    """
    omega_c: float = Field(..., ge=0.0, le=1.0, description="Contradiction score (0=consistent, 1=contradictory)")
    schism: float = Field(..., ge=0.0, le=1.0, description="Disagreement severity across ensemble")
    burst_zscore: float = Field(..., description="Burst detection z-score (>3.0 = anomaly)")
    ood_score: float = Field(..., ge=0.0, le=1.0, description="Out-of-distribution score")
    source_trust: float = Field(..., ge=0.0, le=1.0, description="Source reliability score")
    gaming_risk: Literal["LOW", "MEDIUM", "HIGH"] = Field(..., description="Overall gaming risk classification")


class MergeMetadata(BaseModel):
    """Merge execution metadata."""
    deterministic: bool = Field(..., description="Whether merge is deterministically replayable")
    merge_latency_ms: float = Field(..., ge=0.0)
    conflict_count: Optional[int] = Field(None, ge=0)
    guardian_interventions: Optional[int] = Field(None, ge=0)
    merge_tool_version: Optional[str] = None
    replay_verified: Optional[bool] = None
    replay_verification_time: Optional[datetime] = None


class MergeReceiptV2(BaseModel):
    """
    CHOIR Merge Receipt v2 - Constitutional Computing Edition.

    Key production changes from v1:
    - χ-score computed at query time (removed from storage)
    - proof_refs instead of full proof payloads
    - CloudEvents id/type/time mapping
    - Tri-temporal semantics (valid_time, transaction_time, observed_at optional)
    - Tier inheritance from parents
    """

    # Schema metadata
    schema_: Literal["receipt.merge.v2"] = Field("receipt.merge.v2", alias="schema")
    event_id: str = Field(..., pattern=r"^[0-9A-HJKMNP-TV-Z]{26}$", description="ULID event identifier (maps to CloudEvents id)")
    receipt_id: str = Field(..., pattern=r"^merge-[0-9A-HJKMNP-TV-Z]{26}$", description="Merge receipt ULID (backward compat)")
    event_type: Literal["loom.coordination.merge.v2"] = "loom.coordination.merge.v2"

    # Merge components
    parents: List[ParentReceipt] = Field(..., min_length=2, max_length=1000)
    strategy: MergeStrategy
    conflicts: Optional[List[Conflict]] = None

    # Digests
    digest_before: Digest
    digest_after: Digest

    # Tri-temporal semantics
    valid_time: datetime = Field(..., description="When merge logically occurred")
    transaction_time: datetime = Field(..., description="When merge was recorded")
    observed_at: Optional[datetime] = Field(None, description="When merge was observed (optional)")

    # Multi-tenancy & namespaces
    tenant_id: str = Field(..., pattern=r"^[a-z0-9_-]+$")
    namespaces: Optional[List[str]] = None

    # Tier & retention
    tier: Literal["TRANSIENT", "OPERATIONAL", "RECORDED", "SIGNED", "LEGAL"]
    retention_days: Optional[int] = Field(None, ge=0)

    # Cryptographic signatures
    signature: Optional[str] = Field(None, pattern=r"^ed25519:[A-Za-z0-9+/=]+$")
    pq_signature: Optional[str] = Field(None, pattern=r"^ml-dsa-65:[A-Za-z0-9+/=]+$")

    # Proof references (NOT full payloads)
    proof_refs: Optional[ProofRefs] = None

    # W3C Trace Context
    traceparent: Optional[str] = Field(None, pattern=r"^[0-9a-f]{2}-[0-9a-f]{32}-[0-9a-f]{16}-[0-9a-f]{2}$")
    tracestate: Optional[str] = None

    # Execution metadata
    metadata: MergeMetadata

    # Anti-gaming detection (Ωc composite)
    anti_gaming: Optional[AntiGamingSignals] = Field(None, description="Composite anti-gaming signals")

    # Domain payload
    domain_data: Optional[Dict[str, Any]] = None

    model_config = ConfigDict(
        populate_by_name=True,
        json_schema_extra={
            "example": {
                "schema": "receipt.merge.v2",
                "event_id": "01JCABCDEF1234567890ABCDEF",
                "receipt_id": "merge-01JCABCDEF1234567890ABCDEF",
                "event_type": "loom.coordination.merge.v2",
                "parents": [
                    {
                        "receipt_id": "agent-alice-01JCAB123",
                        "digest": "sha256:a1b2c3d4e5f6...",
                        "agent_id": "agent-alice",
                        "timestamp": "2025-11-07T12:00:00.123Z",
                    }
                ],
                "strategy": {"type": "reconcile"},
                "digest_before": {"algorithm": "sha256", "parents_hash": "sha256:..."},
                "digest_after": {"algorithm": "sha256", "merge_result_hash": "sha256:..."},
                "valid_time": "2025-11-07T12:00:00.500Z",
                "transaction_time": "2025-11-07T12:00:01.234Z",
                "tenant_id": "hospital_stanford",
                "tier": "SIGNED",
                "metadata": {"deterministic": True, "merge_latency_ms": 42.3},
            }
        },
    )

    @field_validator("event_id", mode="before")
    @classmethod
    def generate_event_id_if_missing(cls, v):
        """Generate ULID if not provided."""
        if v is None or v == "":
            return str(ulid.ULID())
        return v

    @field_validator("receipt_id", mode="before")
    @classmethod
    def sync_receipt_id_with_event_id(cls, v, info):
        """Sync receipt_id with event_id for backward compatibility."""
        if v is None or v == "":
            event_id = info.data.get("event_id")
            if event_id:
                return f"merge-{event_id}"
        return v

    @field_validator("transaction_time", mode="before")
    @classmethod
    def default_transaction_time(cls, v):
        """Default transaction_time to now if not provided."""
        if v is None:
            return datetime.now(timezone.utc)
        return v

    @computed_field
    @property
    def parent_count(self) -> int:
        """Number of parent receipts."""
        return len(self.parents)

    @computed_field
    @property
    def conflict_count(self) -> int:
        """Number of conflicts (cached in metadata, computed here as fallback)."""
        if self.metadata.conflict_count is not None:
            return self.metadata.conflict_count
        return len(self.conflicts) if self.conflicts else 0

    def to_cloudevents(self, source: str = "urn:loom:agent:maestro-choir") -> Dict[str, Any]:
        """
        Convert to CloudEvents v1.0 envelope.

        Maps:
        - id ← event_id
        - source ← provided or default
        - type ← event_type
        - time ← valid_time (CE standard)
        - loomtransactiontime ← transaction_time (CE extension)
        - loomobservedat ← observed_at (CE extension)
        - loomtier ← tier (CE extension)
        - loomtenant ← tenant_id (CE extension)
        """
        envelope = {
            "specversion": "1.0",
            "id": self.event_id,
            "source": source,
            "type": self.event_type,
            "time": self.valid_time.isoformat(),
            "datacontenttype": "application/json",
            "dataschema": "https://loom.internal/schemas/receipt.merge.v2.json",
            "data": self.model_dump(by_alias=True, exclude={"event_id", "event_type"})
        }

        # CE extensions
        envelope["loomtransactiontime"] = self.transaction_time.isoformat()
        if self.observed_at:
            envelope["loomobservedat"] = self.observed_at.isoformat()
        envelope["loomtier"] = self.tier
        envelope["loomtenant"] = self.tenant_id

        # W3C Trace Context
        if self.traceparent:
            envelope["traceparent"] = self.traceparent
        if self.tracestate:
            envelope["tracestate"] = self.tracestate

        # Proof level (if available)
        if self.proof_refs and self.proof_refs.proof_level:
            envelope["loompro oflevel"] = self.proof_refs.proof_level

        return envelope


def compute_effective_tier(parent_tiers: List[str], requested_tier: str = "SIGNED") -> str:
    """
    Compute effective tier for merge based on parent tiers.

    Rules:
    - Merge tier = max(parent tiers, requested tier)
    - Tier hierarchy: TRANSIENT < OPERATIONAL < RECORDED < SIGNED < LEGAL
    """
    tier_hierarchy = ["TRANSIENT", "OPERATIONAL", "RECORDED", "SIGNED", "LEGAL"]

    max_parent_tier_idx = max(
        (tier_hierarchy.index(t) for t in parent_tiers if t in tier_hierarchy),
        default=0
    )
    requested_tier_idx = tier_hierarchy.index(requested_tier) if requested_tier in tier_hierarchy else 0

    effective_idx = max(max_parent_tier_idx, requested_tier_idx)
    return tier_hierarchy[effective_idx]


def compute_retention_days(tier: str) -> int:
    """Compute retention days from tier."""
    retention_map = {
        "TRANSIENT": 0,
        "OPERATIONAL": 1,
        "RECORDED": 30,
        "SIGNED": 365,
        "LEGAL": 2190  # 6 years (HIPAA)
    }
    return retention_map.get(tier, 0)


class ArchiveReceipt(BaseModel):
    """
    Receipt documenting monthly log compaction to archive.

    Emitted when receipts older than 90 days are compacted from hot tier
    (.loom/receipts/log/) to archive tier (.loom/receipts/archive/YYYY-MM/).
    """

    # Schema metadata
    schema_: Literal["receipt.archive.v1"] = Field("receipt.archive.v1", alias="schema")
    receipt_id: str = Field(
        ...,
        pattern=r"^archive-[0-9A-HJKMNP-TV-Z]{26}$",
        description="Archive receipt ULID (e.g., archive-01JCABCDEF1234567890ABCDEF)"
    )

    # Archive metadata
    archive_month: str = Field(
        ...,
        pattern=r"^\d{4}-\d{2}$",
        description="Month archived (YYYY-MM format)"
    )
    archived_receipts_count: int = Field(
        ...,
        ge=0,
        description="Number of receipts archived in this compaction"
    )
    archive_path: str = Field(
        ...,
        description="Relative path to archive file (e.g., archive/2025-11/receipts.jsonl.gz)"
    )
    hash_of_archive: str = Field(
        ...,
        pattern=r"^(sha256|blake3):[a-f0-9]{64}$",
        description="Cryptographic hash of compressed archive file"
    )

    # Tri-temporal semantics
    valid_time: datetime = Field(..., description="When archival logically occurred")
    transaction_time: datetime = Field(..., description="When archival was recorded")
    observed_at: Optional[datetime] = Field(None, description="When archival was observed")

    # Multi-tenancy
    tenant_id: str = Field(default="default", pattern=r"^[a-z0-9_-]+$")

    # Tier & retention
    tier: Literal["SIGNED", "LEGAL"] = "SIGNED"
    retention_days: Optional[int] = Field(default=2190, ge=0)  # 6 years default

    # Cryptographic signature
    signature: Optional[str] = Field(None, pattern=r"^ed25519:[A-Za-z0-9+/=]+$")
    signed_at: Optional[datetime] = None

    # W3C Trace Context
    traceparent: Optional[str] = Field(None, pattern=r"^[0-9a-f]{2}-[0-9a-f]{32}-[0-9a-f]{16}-[0-9a-f]{2}$")

    model_config = ConfigDict(
        populate_by_name=True,
        json_schema_extra={
            "example": {
                "schema": "receipt.archive.v1",
                "receipt_id": "archive-01JCABCDEF1234567890ABCDEF",
                "archive_month": "2025-11",
                "archived_receipts_count": 1234,
                "archive_path": "archive/2025-11/receipts.jsonl.gz",
                "hash_of_archive": "sha256:a1b2c3d4e5f6...",
                "valid_time": "2025-12-01T00:00:00Z",
                "transaction_time": "2025-12-01T00:00:05Z",
                "tenant_id": "default",
                "tier": "SIGNED",
            }
        },
    )
