"""CHOIR merge receipts."""

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

from receipts.domains.governance import GovernanceReceipt
from receipts.compat.pyd import BaseModel, ConfigDictLike, Field, field_validator, model_validator


class HunkMerged(BaseModel):
    """A single hunk merged in this operation."""

    claim_id: str = Field(..., description="Hunk claim ID")
    start_line: int = Field(..., ge=1, description="Starting line number")
    end_line: int = Field(..., ge=1, description="Ending line number")
    agent_id: Optional[str] = Field(None, description="Agent that claimed this hunk")

    model_config = ConfigDictLike(frozen=True)

    @model_validator(mode='after')
    def validate_line_range(self) -> 'HunkMerged':
        """Ensure end_line >= start_line."""
        if self.end_line < self.start_line:
            raise ValueError("end_line must be >= start_line")
        return self


class MergeConflict(BaseModel):
    """A merge conflict encountered."""
    
    line_range: str = Field(..., description="Conflict line range (e.g., '42-58')")
    conflicting_claim: str = Field(..., description="Conflicting claim ID")

    model_config = ConfigDictLike(frozen=True)


class MergeMetadata(BaseModel):
    """Additional merge operation metadata."""
    
    merge_strategy: Optional[str] = Field(
        "micro-merge",
    description="Merge strategy used")
    diff_size_bytes: Optional[int] = Field(None, description="Size of merged diff")
    latency_ms: Optional[float] = Field(None, description="Merge operation latency")

    model_config = ConfigDictLike(frozen=True)


class MergeReceipt(GovernanceReceipt):
    """
    Receipt for file merge operation in CHOIR coordination.
    
    Documents each micro-merge operation including Guardian verdict,
    hunks merged, conflicts, and final status.
    """
    
    receipt_type: Literal["MergeReceipt"] = Field(default="MergeReceipt")
    domain: str = Field(default="merge")
    organ: str = Field(default="choir")

    profile: str = Field(default="governance", description="Profile controlling signing/retention")
    receipt_mode: str = Field(default="signed", description="Receipt mode ('signed'|'minimal')")
    retention_days: Optional[int] = Field(default=365, description="Retention policy in days")
    encryption_required: bool = Field(default=True, description="Whether encryption is required")

    schema: Literal["receipt.merge.v1"] = Field(default="receipt.merge.v1")
    receipt_id: str = Field(..., pattern=r"^merge-[0-9A-Za-z_-]+$")
    
    # Agent info
    agent_id: str = Field(..., description="Agent that performed the merge")
    session_id: Optional[str] = Field(None, description="CHOIR session ID")
    
    # File info
    file_path: str = Field(..., description="Path to file being merged")
    base_sha: Optional[str] = Field(
        None,
        pattern=r"^[0-9a-f]{40}$",
        description="Git SHA of base commit"
    )
    
    # Merge details
    hunks_merged: List[HunkMerged] = Field(
        ...,
        description="List of hunks merged in this operation"
    )
    
    # Guardian verdict
    guardian_verdict: Literal["pass", "refusal", "error"] = Field(
        ...,
        description="Guardian's verdict on the merge"
    )
    guardian_receipt_id: Optional[str] = Field(
        None,
        description="Reference to Guardian's receipt"
    )
    dignity_score: Optional[float] = Field(
        None,
        ge=0.0,
        le=1.0,
        description="EVHD dignity score"
    )
    
    # Perception lineage snapshot (if merge occurred during active perception session)
    perception_lineage_snapshot: Optional[Dict[str, Any]] = Field(
        None,
        description="Snapshot of perception lineage state at merge time (session_id, node_count, edge_count, etc.)"
    )
    
    # Status
    status: Literal["applied", "blocked", "error", "pending"] = Field(
        ...,
        description="Merge operation status"
    )
    error_message: Optional[str] = Field(None, description="Error details if status=error")
    conflicts: List[MergeConflict] = Field(
        default_factory=list,
        description="Any merge conflicts encountered"
    )
    
    # Provenance
    timestamp: str = Field(
        default_factory=lambda: datetime.now(timezone.utc).isoformat(),
        description="ISO 8601 timestamp for the merge operation",
    )
    # Legacy field kept for backward compatibility; signatures are primarily stored
    # on the base model as typed `signatures` envelopes. Make this optional so
    # _bind_signatures in BaseReceipt can coerce mapping envelopes into the
    # typed `signatures` list without triggering a missing-field validation.
    signature: Optional[str] = Field(
        default=None,
        pattern=r"^ed25519:.+$",
        description="Merge-specific signature material (legacy, optional)",
    )

    # Metadata
    metadata: Optional[MergeMetadata] = Field(None, description="Additional metadata")

    @model_validator(mode='after')
    def validate_status_consistency(self) -> 'MergeReceipt':
        """Ensure status is consistent with verdict."""
        # If Guardian refused, status should be blocked (not applied)
        if self.guardian_verdict == 'refusal' and self.status == 'applied':
            raise ValueError("Cannot apply merge when Guardian verdict is 'refusal'")

        # If Guardian passed, status should not be blocked
        if self.guardian_verdict == 'pass' and self.status == 'blocked':
            raise ValueError("Cannot block merge when Guardian verdict is 'pass'")

        return self
    
    def to_dict(self) -> Dict[str, Any]:
        """Convert to dictionary for JSON serialization."""
        data: Dict[str, Any] = super().to_dict()
        data["schema"] = self.schema
        data["timestamp"] = self.timestamp
        data["signature"] = self.signature
        data["profile"] = self.profile
        data["receipt_mode"] = self.receipt_mode
        data["retention_days"] = self.retention_days
        data["encryption_required"] = self.encryption_required
        if self.perception_lineage_snapshot:
            data["perception_lineage_snapshot"] = self.perception_lineage_snapshot
        return data
    
    def hunks_count(self) -> int:
        """Get count of hunks merged."""
        return len(self.hunks_merged)
    
    def has_conflicts(self) -> bool:
        """Check if merge had conflicts."""
        return len(self.conflicts) > 0
    
    def is_success(self) -> bool:
        """Check if merge was successful."""
        return self.status == "applied" and self.guardian_verdict == "pass"
    
    def has_lineage(self) -> bool:
        """Check if merge includes perception lineage snapshot."""
        return self.perception_lineage_snapshot is not None
    
    def get_lineage_session_id(self) -> Optional[str]:
        """Get session ID from lineage snapshot if present."""
        if self.perception_lineage_snapshot:
            return self.perception_lineage_snapshot.get("session_id")
        return None


# Example usage
if __name__ == "__main__":
    import json
    
    print("MergeReceipt Demo")
    print("=" * 70)
    
    # Example 1: Successful merge
    receipt_success = MergeReceipt(
        receipt_id="merge-test_file-py-1-50-20251107T083000",
        agent_id="agent-alice",
        session_id="choir-session-abc123",
        file_path="src/test_file.py",
        base_sha="a1b2c3d4e5f6789012345678901234567890abcd",
        hunks_merged=[
            HunkMerged(
                claim_id="claim-1",
                start_line=10,
                end_line=25,
                agent_id="agent-alice"
            ),
            HunkMerged(
                claim_id="claim-2",
                start_line=40,
                end_line=50,
                agent_id="agent-alice"
            )
        ],
        guardian_verdict="pass",
        guardian_receipt_id="receipt-guardian-xyz789",
        dignity_score=0.92,
        status="applied",
        timestamp="2025-11-07T08:30:00Z",
        signature="ed25519:placeholder_signature_success",
        metadata=MergeMetadata(
            merge_strategy="micro-merge",
            diff_size_bytes=1024,
            latency_ms=45.2
        )
    )
    
    print("\n✅ Example 1: Successful Merge")
    print(json.dumps(receipt_success.to_dict(), indent=2))
    print(f"  - Hunks merged: {receipt_success.hunks_count()}")
    print(f"  - Success: {receipt_success.is_success()}")
    print(f"  - Conflicts: {receipt_success.has_conflicts()}")
    
    # Example 2: Blocked merge (dignity violation)
    receipt_blocked = MergeReceipt(
        receipt_id="merge-test_file-py-51-100-20251107T083100",
        agent_id="agent-bob",
        session_id="choir-session-abc123",
        file_path="src/test_file.py",
        hunks_merged=[
            HunkMerged(
                claim_id="claim-3",
                start_line=51,
                end_line=100,
                agent_id="agent-bob"
            )
        ],
        guardian_verdict="refusal",
        guardian_receipt_id="receipt-guardian-abc456",
        dignity_score=0.62,  # Below threshold
        status="blocked",
        error_message="Dignity score 0.62 below threshold 0.70",
        timestamp="2025-11-07T08:31:00Z",
        signature="ed25519:placeholder_signature_blocked"
    )
    
    print("\n\n❌ Example 2: Blocked Merge (Dignity Violation)")
    print(json.dumps(receipt_blocked.to_dict(), indent=2))
    print(f"  - Hunks merged: {receipt_blocked.hunks_count()}")
    print(f"  - Success: {receipt_blocked.is_success()}")
    print(f"  - Dignity score: {receipt_blocked.dignity_score}")
    
    # Example 3: Merge with conflicts
    receipt_conflict = MergeReceipt(
        receipt_id="merge-test_file-py-20-30-20251107T083200",
        agent_id="agent-charlie",
        session_id="choir-session-abc123",
        file_path="src/test_file.py",
        hunks_merged=[
            HunkMerged(
                claim_id="claim-4",
                start_line=20,
                end_line=30,
                agent_id="agent-charlie"
            )
        ],
        guardian_verdict="pass",
        dignity_score=0.85,
        status="applied",
        conflicts=[
            MergeConflict(
                line_range="22-24",
                conflicting_claim="claim-1"
            )
        ],
        timestamp="2025-11-07T08:32:00Z",
        signature="ed25519:placeholder_signature_conflict",
        metadata=MergeMetadata(
            merge_strategy="micro-merge",
            diff_size_bytes=512,
            latency_ms=120.5
        )
    )
    
    print("\n\n⚠️  Example 3: Merge with Conflicts (Still Applied)")
    print(json.dumps(receipt_conflict.to_dict(), indent=2))
    print(f"  - Hunks merged: {receipt_conflict.hunks_count()}")
    print(f"  - Success: {receipt_conflict.is_success()}")
    print(f"  - Conflicts: {receipt_conflict.has_conflicts()} ({len(receipt_conflict.conflicts)} conflicts)")
    
    print("\n" + "=" * 70)
    print("✅ MergeReceipt models working correctly")
