"""
Idempotent Receipt Writer (Content-Hash Deduplication)

Prevents duplicate receipts from wasting storage by using Blake3 content-hash
deduplication. Receipts with identical content (excluding signature) are stored
only once, with subsequent writes returning the existing receipt_id.

Key Features:
- Content-hash based deduplication (Blake3)
- Persistent hash index for fast lookups
- Signature excluded from hash (for determinism)
- Atomic writes with index updates
- Zero hash collisions in practice

Reference: WORK_BREAKDOWN_PARALLEL_TEAMS_2025-11-06.md (Workstream C.4)
"""

import json
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Dict, Optional

from api.hashing import hash_content


class IdempotentWriter:
    """
    Write receipts with content-hash deduplication.

    Prevents storage waste by detecting duplicate receipts.
    """

    def __init__(self, base_dir: Path = None, root: Path = None, environment: Optional[str] = None, **_: Any):
        """
        Initialize idempotent writer.

        Args:
            base_dir: Base directory for receipts (default: receipts/)
        """
        self.base_dir = root or base_dir or Path("receipts")
        self.environment = environment
        self.base_dir.mkdir(parents=True, exist_ok=True)

        # Hash index: hash → receipt_id
        self.hash_index: Dict[str, str] = {}
        self._load_index()

        # Stats
        self.stats = {
            "writes_total": 0,
            "duplicates_detected": 0,
            "unique_receipts": 0
        }

    def _load_index(self) -> None:
        """Load hash index from disk."""
        index_path = self.base_dir / ".hash_index.json"
        if index_path.exists():
            try:
                with open(index_path, 'r', encoding='utf-8') as f:
                    self.hash_index = json.load(f)
            except (json.JSONDecodeError, IOError) as e:
                print(f"Warning: Failed to load hash index: {e}")
                print("Starting with empty index...")
                self.hash_index = {}

    def _save_index(self) -> None:
        """Save hash index to disk (atomic write)."""
        index_path = self.base_dir / ".hash_index.json"
        temp_path = index_path.with_suffix(".json.tmp")

        # Write to temp file first
        with open(temp_path, 'w', encoding='utf-8') as f:
            json.dump(self.hash_index, f, indent=2, sort_keys=True)

        # Atomic rename
        temp_path.replace(index_path)

    _EXCLUDED_FIELDS = {
        "signature",
        "signatures",
        "timestamp",
        "ts",
        "system_time",
        "receipt_hash",
        "valid_time",
        # RFC 3161 timestamp fields (vary by TSA server/time)
        "rfc3161_tst",
        "rfc3161_timestamp",
        # Rekor transparency log fields (vary by log state)
        "transparency",
        "rekor_entry_uuid",
        "rekor_log_index",
        # Legal notice fields (vary by service time)
        "legal_notice",
        # Custodian attestation (may be added post-creation)
        "custodian_attestation",
    }

    def _compute_content_hash(self, receipt: Dict[str, Any]) -> str:
        """
        Compute content hash for receipt.

        Excludes signature field for determinism (signatures vary by timestamp).

        Args:
            receipt: Receipt dictionary

        Returns:
            Blake3 hash string
        """
        # Exclude signature and timestamp for deterministic hashing
        content = {
            k: v
            for k, v in receipt.items()
            if k not in self._EXCLUDED_FIELDS
        }

        return hash_content(content)

    def _get_receipt_dir(self, schema: str) -> Path:
        """
        Get directory for receipt type.

        Args:
            schema: Receipt schema (e.g., "receipt.echo.v1")

        Returns:
            Directory path for this receipt type
        """
        # Extract receipt type from schema
        # e.g., "receipt.echo.v1" → "echo"
        parts = schema.split(".")
        if len(parts) >= 2 and parts[0] == "receipt":
            receipt_type = parts[1]
        else:
            receipt_type = "unknown"

        receipt_dir = self.base_dir / receipt_type
        receipt_dir.mkdir(parents=True, exist_ok=True)

        return receipt_dir

    def write_receipt(
        self,
        receipt: Optional[Dict[str, Any]] = None,
        *,
        content: Optional[Dict[str, Any]] = None,
        force_write: bool = False,
        **_: Any,
    ) -> Dict[str, Any]:
        """
        Write receipt with deduplication.

        Args:
            receipt: Receipt dictionary (must have receipt_id and schema)
            content: Optional alias for receipt payload
            force_write: If True, bypass deduplication and always write

        Returns:
            Dict with:
                - receipt_id: str (existing if duplicate, new if unique)
                - is_duplicate: bool
                - content_hash: str (Blake3 hash)
                - path: str (file path)
        """
        payload = receipt or content
        if payload is None:
            raise ValueError("Receipt payload is required")

        self.stats["writes_total"] += 1

        # Validate receipt has required fields
        if "receipt_id" not in payload:
            raise ValueError("Receipt must have 'receipt_id' field")
        if "schema" not in payload:
            raise ValueError("Receipt must have 'schema' field")

        # Compute content hash
        content_hash = self._compute_content_hash(payload)

        # Check if duplicate (unless force_write)
        if not force_write and content_hash in self.hash_index:
            existing_id = self.hash_index[content_hash]
            self.stats["duplicates_detected"] += 1

            print(
                f"Duplicate receipt detected (hash={content_hash[:16]}...), "
                f"using existing: {existing_id}"
            )

            return {
                "receipt_id": existing_id,
                "is_duplicate": True,
                "content_hash": content_hash,
                "path": self._find_receipt_path(existing_id, payload["schema"])
            }

        # Write new receipt
        receipt_id = payload["receipt_id"]
        schema = payload["schema"]

        receipt_dir = self._get_receipt_dir(schema)
        path = receipt_dir / f"{receipt_id}.json"

        # Ensure timestamp is present
        if "timestamp" not in payload and "ts" not in payload:
            payload["timestamp"] = datetime.now(timezone.utc).isoformat()

        # Write atomically
        temp_path = path.with_suffix(".json.tmp")
        with open(temp_path, 'w', encoding='utf-8') as f:
            json.dump(payload, f, indent=2, ensure_ascii=False)

        temp_path.replace(path)

        # Update index
        self.hash_index[content_hash] = receipt_id
        self._save_index()

        self.stats["unique_receipts"] += 1

        return {
            "receipt_id": receipt_id,
            "is_duplicate": False,
            "content_hash": content_hash,
            "path": str(path)
        }

    def check_duplicate(
        self,
        receipt: Dict[str, Any],
        *,
        force_write: bool = False,
    ) -> Dict[str, Any]:
        """
        Check whether a receipt payload already exists.

        Returns:
            dict with:
              - is_duplicate: bool
              - receipt_id: Optional[str]
              - content_hash: str
        """
        self.stats["writes_total"] += 1
        content_hash = self._compute_content_hash(receipt)
        existing_id = self.hash_index.get(content_hash)
        if not force_write and existing_id:
            self.stats["duplicates_detected"] += 1
            return {
                "is_duplicate": True,
                "receipt_id": existing_id,
                "content_hash": content_hash,
            }
        return {
            "is_duplicate": False,
            "receipt_id": None,
            "content_hash": content_hash,
        }

    def register_unique(
        self,
        receipt: Dict[str, Any],
        *,
        content_hash: Optional[str] = None,
    ) -> str:
        """
        Register a unique receipt in the dedupe index without writing payload.

        Returns:
            The content hash that was registered.
        """
        if "receipt_id" not in receipt:
            raise ValueError("Receipt must have 'receipt_id' field")
        token = content_hash or self._compute_content_hash(receipt)
        self.hash_index[token] = receipt["receipt_id"]
        self._save_index()
        self.stats["unique_receipts"] += 1
        return token

    def _find_receipt_path(self, receipt_id: str, schema: str) -> str:
        """
        Find path for existing receipt.

        Args:
            receipt_id: Receipt ID
            schema: Receipt schema

        Returns:
            File path (relative or absolute)
        """
        receipt_dir = self._get_receipt_dir(schema)
        path = receipt_dir / f"{receipt_id}.json"

        if path.exists():
            return str(path)
        else:
            # Fallback: search all receipt types
            for candidate in self.base_dir.rglob(f"{receipt_id}.json"):
                return str(candidate)

            return f"<not found: {receipt_id}>"

    def batch_write(
        self,
        receipts: list[Dict[str, Any]],
        force_write: bool = False
    ) -> list[Dict[str, Any]]:
        """
        Write multiple receipts in batch.

        Args:
            receipts: List of receipt dictionaries
            force_write: If True, bypass deduplication

        Returns:
            List of write results (same format as write_receipt)
        """
        results = []
        for receipt in receipts:
            result = self.write_receipt(receipt, force_write=force_write)
            results.append(result)

        return results

    def get_stats(self) -> Dict[str, Any]:
        """
        Get writer statistics.

        Returns:
            Dictionary with:
                - writes_total: int
                - duplicates_detected: int
                - unique_receipts: int
                - deduplication_rate: float (0.0-1.0)
                - index_size: int (number of hashes)
        """
        total = self.stats["writes_total"]
        duplicates = self.stats["duplicates_detected"]

        return {
            "writes_total": total,
            "duplicates_detected": duplicates,
            "unique_receipts": self.stats["unique_receipts"],
            "deduplication_rate": duplicates / total if total > 0 else 0.0,
            "index_size": len(self.hash_index)
        }

    def reset_stats(self) -> None:
        """Reset statistics counters."""
        self.stats = {
            "writes_total": 0,
            "duplicates_detected": 0,
            "unique_receipts": 0
        }

    def rebuild_index(self) -> None:
        """
        Rebuild hash index from existing receipts.

        Scans all receipt files and recreates the hash index.
        Useful for recovery or migration.
        """
        print("Rebuilding hash index from existing receipts...")
        self.hash_index = {}

        receipt_count = 0
        for receipt_file in self.base_dir.rglob("*.json"):
            # Skip the index file itself
            if receipt_file.name == ".hash_index.json":
                continue

            try:
                with open(receipt_file, 'r', encoding='utf-8') as f:
                    receipt = json.load(f)

                if "receipt_id" in receipt:
                    content_hash = self._compute_content_hash(receipt)
                    receipt_id = receipt["receipt_id"]

                    # Only store if not duplicate
                    if content_hash not in self.hash_index:
                        self.hash_index[content_hash] = receipt_id
                        receipt_count += 1

            except (json.JSONDecodeError, IOError) as e:
                print(f"Warning: Failed to process {receipt_file}: {e}")

        self._save_index()
        print(f"Index rebuilt: {receipt_count} unique receipts indexed")


# Example usage
if __name__ == "__main__":
    import time

    writer = IdempotentWriter(base_dir=Path("receipts_test"))

    print("IdempotentWriter Demo")
    print("=" * 70)

    # Create sample receipt
    receipt1 = {
        "schema": "receipt.echo.v1",
        "receipt_id": "receipt-01JC8X9YZW3K2M4N5P6Q7R8SAB",
        "agent_id": "echo-agent",
        "intent_id": "intent-01JC8X9YZW3K2M4N5P6Q7R",
        "verdict": "pass",
        "input_text": "Hello, world!",
        "output_text": "Hello, world!",
        "signature": "ed25519:abc123..."
    }

    # Write #1 (new)
    print("\nWrite #1 (should be unique):")
    result1 = writer.write_receipt(receipt1)
    print(f"  receipt_id: {result1['receipt_id']}")
    print(f"  is_duplicate: {result1['is_duplicate']}")
    print(f"  content_hash: {result1['content_hash'][:32]}...")

    # Write #2 (duplicate content, different signature)
    print("\nWrite #2 (same content, different signature):")
    receipt2 = receipt1.copy()
    receipt2["signature"] = "ed25519:xyz789..."  # Different signature
    receipt2["timestamp"] = time.time()  # Different timestamp

    result2 = writer.write_receipt(receipt2)
    print(f"  receipt_id: {result2['receipt_id']}")
    print(f"  is_duplicate: {result2['is_duplicate']}")
    print(f"  content_hash: {result2['content_hash'][:32]}...")

    # Write #3 (different content)
    print("\nWrite #3 (different content):")
    receipt3 = receipt1.copy()
    receipt3["receipt_id"] = "receipt-01JC8X9YZW3K2M4N5P6Q7R8SAC"
    receipt3["input_text"] = "Different input"
    receipt3["output_text"] = "Different output"

    result3 = writer.write_receipt(receipt3)
    print(f"  receipt_id: {result3['receipt_id']}")
    print(f"  is_duplicate: {result3['is_duplicate']}")
    print(f"  content_hash: {result3['content_hash'][:32]}...")

    # Stats
    print("\nStatistics:")
    stats = writer.get_stats()
    for key, value in stats.items():
        print(f"  {key}: {value}")

    print(f"\n✅ Deduplication working: {result2['is_duplicate']}")
