"""
Coordination Receipts (Multi-Agent Coordination Tracking)

Tracks coordination events in multi-agent systems:
- Claim: Agent claims work on a resource
- Join: Agent joins a coordination session
- Handoff: Agent transfers work to another agent
- Release: Agent releases a claim
- Timeout: Claim expired

Key Features:
- Resource tracking (files, tasks, hunks, directories, services)
- Agent coordination (who's doing what)
- TTL/expiration for claims
- Guardian integration (policy checks on coordination)
- Dependency tracking (coordination receipt chains)

Reference: WORK_BREAKDOWN_PARALLEL_TEAMS_2025-11-06.md (Workstream D.2.2)
"""

from datetime import datetime, timedelta, timezone
from typing import Dict, List, Literal, Optional
from pydantic import BaseModel, Field, field_validator, model_validator


class ResourceLineRange(BaseModel):
    """Line range for file resources."""

    start: int = Field(..., ge=1, description="Starting line number")
    end: int = Field(..., ge=1, description="Ending line number")

    @model_validator(mode='after')
    def validate_line_range(self) -> 'ResourceLineRange':
        """Ensure end_line >= start_line."""
        if self.end < self.start:
            raise ValueError(f"end line {self.end} must be >= start line {self.start}")
        return self


class CoordinationResource(BaseModel):
    """Resource being coordinated."""

    type: Literal["file", "task", "hunk", "directory", "service"] = Field(
        ...,
        description="Resource type being coordinated"
    )
    identifier: str = Field(..., description="Resource identifier (path, task ID, etc.)")
    line_range: Optional[ResourceLineRange] = Field(None, description="Line range for file resources")
    base_sha: Optional[str] = Field(
        None,
        pattern=r"^[0-9a-f]{40}$",
        description="Git SHA if applicable"
    )


class CoordinationReceipt(BaseModel):
    """
    Receipt documenting multi-agent coordination events.

    Tracks claim, join, handoff, release, and timeout events across agents.
    """

    schema: Literal["receipt.coordination.v1"] = "receipt.coordination.v1"

    receipt_id: str = Field(
        ...,
        pattern=r"^coord-[0-9A-Za-z_-]+$",
        description="Unique coordination receipt ID"
    )

    event_type: Literal["claim", "join", "handoff", "release", "timeout"] = Field(
        ...,
        description="Type of coordination event"
    )

    agent_id: str = Field(..., description="Agent performing the action")

    session_id: str = Field(
        ...,
        description="CHOIR session or coordination context ID"
    )

    resource: CoordinationResource = Field(
        ...,
        description="Resource being coordinated"
    )

    target_agent_id: Optional[str] = Field(
        None,
        description="Target agent for handoff events"
    )

    claim_id: Optional[str] = Field(
        None,
        description="Claim ID being referenced (for release/handoff)"
    )

    status: Literal["active", "completed", "cancelled", "expired"] = Field(
        ...,
        description="Coordination event status"
    )

    ttl_seconds: Optional[int] = Field(
        None,
        ge=0,
        description="Time-to-live for claims (0 = no expiration)"
    )

    expires_at: Optional[str] = Field(
        None,
        description="ISO 8601 expiration timestamp for claims"
    )

    reason: Optional[str] = Field(
        None,
        description="Reason for coordination event (handoff reason, release reason, etc.)"
    )

    guardian_verdict: Optional[Literal["pass", "refusal", "error"]] = Field(
        None,
        description="Guardian's verdict on coordination event"
    )

    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 for coordination event"
    )

    priority: Optional[int] = Field(
        None,
        ge=0,
        le=10,
        description="Priority level (0=lowest, 10=highest)"
    )

    dependencies: Optional[List[str]] = Field(
        default_factory=list,
        description="IDs of coordination receipts this depends on"
    )

    timestamp: str = Field(
        default_factory=lambda: datetime.now(timezone.utc).isoformat(),
        description="ISO 8601 timestamp"
    )

    signature: str = Field(
        ...,
        pattern=r"^ed25519:.+$",
        description="Ed25519 signature"
    )

    metadata: Optional[dict] = Field(
        None,
        description="Optional metadata (coordination_protocol, conflict_resolution, latency_ms)"
    )

    @model_validator(mode='after')
    def validate_handoff_has_target(self) -> 'CoordinationReceipt':
        """Ensure handoff events have a target_agent_id."""
        if self.event_type == "handoff" and not self.target_agent_id:
            raise ValueError("handoff events must specify target_agent_id")
        return self

    @model_validator(mode='after')
    def validate_release_has_claim_id(self) -> 'CoordinationReceipt':
        """Ensure release events reference a claim_id."""
        if self.event_type == "release" and not self.claim_id:
            raise ValueError("release events must reference a claim_id")
        return self

    @model_validator(mode='after')
    def validate_expires_at_consistency(self) -> 'CoordinationReceipt':
        """Ensure expires_at is consistent with ttl_seconds."""
        if self.ttl_seconds is not None and self.ttl_seconds > 0:
            # Calculate expected expiration
            ts = datetime.fromisoformat(self.timestamp.replace('Z', '+00:00'))
            expected_expiration = ts + timedelta(seconds=self.ttl_seconds)

            if self.expires_at:
                # Validate provided expiration is reasonable
                expires = datetime.fromisoformat(self.expires_at.replace('Z', '+00:00'))
                delta = abs((expires - expected_expiration).total_seconds())

                # Allow 1 second tolerance for clock skew
                if delta > 1.0:
                    raise ValueError(
                        f"expires_at ({self.expires_at}) inconsistent with "
                        f"ttl_seconds ({self.ttl_seconds})"
                    )

        return self


def create_claim_receipt(
    agent_id: str,
    session_id: str,
    resource: CoordinationResource,
    ttl_seconds: int = 3600,
    priority: int = 5,
    signature: str = "ed25519:stub"
) -> CoordinationReceipt:
    """
    Create a claim receipt.

    Args:
        agent_id: Agent claiming the resource
        session_id: Coordination session ID
        resource: Resource being claimed
        ttl_seconds: Time-to-live for claim (default: 1 hour)
        priority: Priority level (default: 5)
        signature: Ed25519 signature

    Returns:
        CoordinationReceipt for claim event
    """
    timestamp = datetime.now(timezone.utc)
    expires_at = timestamp + timedelta(seconds=ttl_seconds)

    receipt_id = f"coord-claim-{agent_id}-{int(timestamp.timestamp())}"

    return CoordinationReceipt(
        receipt_id=receipt_id,
        event_type="claim",
        agent_id=agent_id,
        session_id=session_id,
        resource=resource,
        status="active",
        ttl_seconds=ttl_seconds,
        expires_at=expires_at.isoformat(),
        priority=priority,
        timestamp=timestamp.isoformat(),
        signature=signature
    )


def create_handoff_receipt(
    agent_id: str,
    target_agent_id: str,
    session_id: str,
    claim_id: str,
    resource: CoordinationResource,
    reason: str,
    signature: str = "ed25519:stub"
) -> CoordinationReceipt:
    """
    Create a handoff receipt.

    Args:
        agent_id: Agent performing the handoff
        target_agent_id: Agent receiving the work
        session_id: Coordination session ID
        claim_id: Original claim ID being handed off
        resource: Resource being handed off
        reason: Reason for handoff
        signature: Ed25519 signature

    Returns:
        CoordinationReceipt for handoff event
    """
    timestamp = datetime.now(timezone.utc)
    receipt_id = f"coord-handoff-{agent_id}-to-{target_agent_id}-{int(timestamp.timestamp())}"

    return CoordinationReceipt(
        receipt_id=receipt_id,
        event_type="handoff",
        agent_id=agent_id,
        target_agent_id=target_agent_id,
        session_id=session_id,
        claim_id=claim_id,
        resource=resource,
        status="completed",
        reason=reason,
        timestamp=timestamp.isoformat(),
        signature=signature
    )


def create_release_receipt(
    agent_id: str,
    session_id: str,
    claim_id: str,
    resource: CoordinationResource,
    reason: str = "work completed",
    signature: str = "ed25519:stub"
) -> CoordinationReceipt:
    """
    Create a release receipt.

    Args:
        agent_id: Agent releasing the claim
        session_id: Coordination session ID
        claim_id: Claim ID being released
        resource: Resource being released
        reason: Reason for release
        signature: Ed25519 signature

    Returns:
        CoordinationReceipt for release event
    """
    timestamp = datetime.now(timezone.utc)
    receipt_id = f"coord-release-{claim_id}-{int(timestamp.timestamp())}"

    return CoordinationReceipt(
        receipt_id=receipt_id,
        event_type="release",
        agent_id=agent_id,
        session_id=session_id,
        claim_id=claim_id,
        resource=resource,
        status="completed",
        reason=reason,
        timestamp=timestamp.isoformat(),
        signature=signature
    )


# IdempotentWriter integration
def write_coordination_receipt(
    receipt: CoordinationReceipt,
    writer: Optional['IdempotentWriter'] = None,
    force_write: bool = False
) -> Dict:
    """
    Write coordination receipt with deduplication.

    Args:
        receipt: CoordinationReceipt to write
        writer: IdempotentWriter instance (creates default if None)
        force_write: If True, bypass deduplication

    Returns:
        Dict with receipt_id, is_duplicate, content_hash, path
    """
    from receipts.idempotent_writer import IdempotentWriter
    from pathlib import Path

    if writer is None:
        writer = IdempotentWriter(base_dir=Path("receipts"))

    # Convert to dict
    receipt_dict = receipt.model_dump(mode='json', by_alias=True)

    # Write with deduplication
    return writer.write_receipt(receipt_dict, force_write=force_write)


# Example usage
if __name__ == "__main__":
    print("CoordinationReceipt Demo")
    print("=" * 70)

    # Create a resource
    resource = CoordinationResource(
        type="file",
        identifier="tools/coord/validate_observability.py",
        line_range=ResourceLineRange(start=1, end=100),
        base_sha="a" * 40
    )

    # Create claim receipt
    print("\n1. Agent claims resource:")
    claim = create_claim_receipt(
        agent_id="agent-001",
        session_id="session-abc123",
        resource=resource,
        ttl_seconds=3600,
        priority=7
    )
    print(f"   Claim ID: {claim.receipt_id}")
    print(f"   Status: {claim.status}")
    print(f"   Expires: {claim.expires_at}")

    # Create handoff receipt
    print("\n2. Agent hands off to another agent:")
    handoff = create_handoff_receipt(
        agent_id="agent-001",
        target_agent_id="agent-002",
        session_id="session-abc123",
        claim_id=claim.receipt_id,
        resource=resource,
        reason="Agent-001 reached context limit"
    )
    print(f"   Handoff ID: {handoff.receipt_id}")
    print(f"   From: {handoff.agent_id} → To: {handoff.target_agent_id}")
    print(f"   Reason: {handoff.reason}")

    # Create release receipt
    print("\n3. Agent releases claim:")
    release = create_release_receipt(
        agent_id="agent-002",
        session_id="session-abc123",
        claim_id=claim.receipt_id,
        resource=resource,
        reason="work completed successfully"
    )
    print(f"   Release ID: {release.receipt_id}")
    print(f"   Claim: {release.claim_id}")
    print(f"   Status: {release.status}")

    # Test serialization
    print("\n4. Serialization test:")
    claim_json = claim.model_dump(mode='json', by_alias=True)
    print(f"   JSON keys: {list(claim_json.keys())}")
    print(f"   Schema: {claim_json['schema']}")

    print("\n✅ All examples working!")
