"""
ReceiptV1 Writer

Builds and persists ReceiptV1 format receipts with modular proofs,
embedded profile snapshots, and explicit degraded state tracking.

This writer is designed to work alongside the legacy writer during
the migration period. Eventually, all receipt writes should go through
this pathway.

---
feature:
  id: feature.receipt_writer_v1
  name: ReceiptV1 Writer
  kind: module
  summary: Writer for the new modular chain-of-trust receipt format
  owners: ["@codex"]
  stage: production
  tags: [receipts, chain-of-trust, p0]
  depends_on: [feature.receipt_writer]
  inputs: [BaseReceipt, domain, profile_id]
  outputs: [ReceiptV1]
  invariants:
    - Writes are atomic (temp -> final -> fsync)
    - Proofs are modular and extensible
    - Profile snapshot is embedded in receipt
    - Degraded state is explicit, never silent
  risk_axes:
    partial_write: 0.02
    proof_ordering: 0.01
---
"""

from __future__ import annotations

import json
import logging
import os
import uuid
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Dict, List, Optional, TYPE_CHECKING

from receipts.models_v1 import (
    RECEIPT_V1_VERSION,
    ReceiptV1,
    EnvelopeV1,
    DomainPayloadV1,
    ContentDigestProof,
    LocalSignatureProof,
    TsaRfc3161Proof,
    TransparencyLogProof,
    MissingProof,
    ProofStatusV1,
    Domain,
    GuardianVerdict,
    compute_canonical_digest,
)
from receipts.proof_profiles import (
    resolve_profile,
    get_profile_snapshot,
    PROOF_PROFILES,
)
from receipts.canonicalize import to_jcs_bytes
from receipts.sign import get_default_signing_store, SigningUnavailableError

if TYPE_CHECKING:
    from receipts.models import BaseReceipt

logger = logging.getLogger("loom.receipts.writer_v1")

__all__ = [
    "build_receipt_v1",
    "write_receipt_v1",
    "ReceiptV1BuildError",
]


class ReceiptV1BuildError(Exception):
    """Raised when a ReceiptV1 cannot be built."""

    def __init__(self, message: str, *, reason: str, details: Optional[Dict[str, Any]] = None):
        super().__init__(message)
        self.reason = reason
        self.details = details or {}


def _now_utc() -> datetime:
    """Return current UTC time with millisecond precision."""
    now = datetime.now(timezone.utc)
    if now.microsecond:
        now = now.replace(microsecond=(now.microsecond // 1000) * 1000)
    return now


def _resolve_domain(receipt: "BaseReceipt") -> Domain:
    """Map legacy domain to Domain enum."""
    domain_value = receipt.domain
    if hasattr(domain_value, "value"):
        domain_value = domain_value.value

    domain_str = str(domain_value).lower()

    # Map known domains to the Domain enum values
    domain_map = {
        "governance": Domain.GOVERNANCE,
        "clinical": Domain.CLINICAL,
        "finance": Domain.FINANCE,
        "financial": Domain.FINANCE,
        "ops": Domain.OPS,
        "sandbox": Domain.SANDBOX,
        "markets": Domain.MARKETS,
        "mlb": Domain.MLB,
        "development": Domain.SANDBOX,  # development maps to sandbox
    }

    return domain_map.get(domain_str, Domain.SANDBOX)


def _resolve_guardian_verdict(receipt: "BaseReceipt") -> GuardianVerdict:
    """Extract guardian verdict from receipt context or metrics."""
    # Check for explicit guardian verdict
    context = getattr(receipt, "context", {}) or {}
    if isinstance(context, dict):
        verdict = context.get("guardian_verdict")
        if isinstance(verdict, str):
            try:
                return GuardianVerdict(verdict)
            except ValueError:
                pass

    # Check metrics for gate passage
    metrics = getattr(receipt, "metrics", {}) or {}
    if isinstance(metrics, dict):
        gate_status = metrics.get("gate_status") or metrics.get("guardian_status")
        if gate_status == "passed":
            return GuardianVerdict.PASS
        elif gate_status == "blocked":
            return GuardianVerdict.HARD_FAIL
        elif gate_status == "flagged":
            return GuardianVerdict.SOFT_FAIL

    # Check for refusal indicators
    reasons = getattr(receipt, "reasons", None) or []
    if reasons:
        return GuardianVerdict.HARD_FAIL

    # Default to pass for non-refusal receipts
    receipt_type = getattr(receipt, "type", receipt.__class__.__name__)
    if "Refusal" in receipt_type:
        return GuardianVerdict.HARD_FAIL

    return GuardianVerdict.PASS


def _extract_dignity_score(receipt: "BaseReceipt") -> float:
    """Extract dignity score from receipt metrics."""
    # Check typed dignity
    dignity_typed = getattr(receipt, "dignity_typed", None)
    if dignity_typed is not None:
        if hasattr(dignity_typed, "floor_score"):
            return float(dignity_typed.floor_score)
        if isinstance(dignity_typed, dict):
            return float(dignity_typed.get("floor_score", 0.0))

    # Check metrics
    metrics = getattr(receipt, "metrics", {}) or {}
    if isinstance(metrics, dict):
        dignity = metrics.get("dignity") or metrics.get("dignity_score")
        if isinstance(dignity, (int, float)):
            return float(dignity)
        if isinstance(dignity, dict):
            return float(dignity.get("floor_score", 0.0))

    return 0.0


def _extract_coherence_delta(receipt: "BaseReceipt") -> float:
    """Extract coherence delta from receipt metrics."""
    # Check typed coherence
    coherence_typed = getattr(receipt, "coherence_typed", None)
    if coherence_typed is not None:
        if hasattr(coherence_typed, "delta"):
            return float(coherence_typed.delta)
        if isinstance(coherence_typed, dict):
            return float(coherence_typed.get("delta", 0.0))

    # Check metrics
    metrics = getattr(receipt, "metrics", {}) or {}
    if isinstance(metrics, dict):
        coherence = metrics.get("coherence") or metrics.get("coherence_delta")
        if isinstance(coherence, (int, float)):
            return float(coherence)
        if isinstance(coherence, dict):
            return float(coherence.get("delta", 0.0))

    return 0.0


def _build_envelope(
    receipt: "BaseReceipt",
    *,
    attestation_level: str,
    proof_profile_id: str,
    event_time: Optional[datetime] = None,
) -> EnvelopeV1:
    """Build the EnvelopeV1 from a legacy receipt."""
    receipt_id = f"rcpt-v1-{uuid.uuid4()}"

    # Tri-temporal timestamps
    now = _now_utc()
    e_time = event_time or getattr(receipt, "ts", now)
    o_time = getattr(receipt, "observed_time", None) or getattr(receipt, "system_time", None) or now
    r_time = now

    # Extract tenant
    tenant_id = getattr(receipt, "tenant", None) or "default"
    if hasattr(tenant_id, "value"):
        tenant_id = tenant_id.value

    # Extract governance metrics
    domain = _resolve_domain(receipt)
    guardian_verdict = _resolve_guardian_verdict(receipt)
    dignity_score = _extract_dignity_score(receipt)
    coherence_delta = _extract_coherence_delta(receipt)

    return EnvelopeV1(
        receipt_id=receipt_id,
        tenant_id=tenant_id,
        domain=domain,
        attestation_level=attestation_level,
        proof_profile_id=proof_profile_id,
        event_time=e_time,
        observed_time=o_time,
        receipt_time=r_time,
        guardian_verdict=guardian_verdict,
        dignity_score=dignity_score,
        coherence_delta=coherence_delta,
    )


def _build_domain_payload(receipt: "BaseReceipt") -> DomainPayloadV1:
    """Build the domain-specific payload from a legacy receipt."""
    # Get the full payload
    payload = receipt.model_dump(mode="json", by_alias=True)

    # Extract key fields
    receipt_type = payload.get("type") or payload.get("receipt_type") or receipt.__class__.__name__

    # Extract domain for schema version
    domain_value = receipt.domain
    if hasattr(domain_value, "value"):
        domain_value = domain_value.value
    schema_version = f"{domain_value}.{receipt_type.lower()}.v1"

    # Extract relevant payload fields (exclude envelope-level fields)
    envelope_fields = {
        "receipt_id", "domain", "ts", "system_time", "tenant",
        "parent_id", "parent_ids", "parent_hash", "parent_hashes",
        "signatures", "signature", "receipt_hash", "type", "receipt_type",
    }

    domain_data = {k: v for k, v in payload.items() if k not in envelope_fields and v is not None}

    return DomainPayloadV1(
        kind=receipt_type,
        schema_version=schema_version,
        data=domain_data,
    )


def _attach_content_digest(receipt_v1: ReceiptV1) -> ContentDigestProof:
    """Compute and attach the content digest proof."""
    digest_b64 = compute_canonical_digest(receipt_v1)

    return ContentDigestProof(
        value=digest_b64,
    )


def _attach_local_signature(
    receipt_v1: ReceiptV1,
    content_digest: ContentDigestProof,
    tenant: str = "default",
) -> Optional[LocalSignatureProof]:
    """Sign the content digest with the local Ed25519 key."""
    import base64

    try:
        from nacl.signing import SigningKey
        from nacl.encoding import RawEncoder

        # Get signing key from store
        store = get_default_signing_store()
        key_record = store.ensure_key(tenant)
        if key_record is None:
            logger.warning("No signing key available for ReceiptV1")
            return None

        # Decode the stored secret key
        secret_key_bytes = base64.b64decode(key_record.secret_key_b64)
        signing_key = SigningKey(secret_key_bytes)

        # Sign the digest bytes
        digest_bytes = base64.b64decode(content_digest.value)
        signature = signing_key.sign(digest_bytes, encoder=RawEncoder)
        signature_b64 = base64.b64encode(signature.signature).decode("ascii")

        # Get public key fingerprint
        verify_key = signing_key.verify_key
        pubkey_bytes = verify_key.encode()
        import hashlib

        fingerprint = hashlib.sha256(pubkey_bytes).hexdigest()[:16]

        return LocalSignatureProof(
            kid=f"local:{fingerprint}",
            sig=signature_b64,
        )

    except ImportError:
        logger.warning("nacl not available for signing")
        return None
    except SigningUnavailableError as e:
        logger.warning(f"Signing unavailable: {e}")
        return None
    except Exception as e:
        logger.exception(f"Failed to sign ReceiptV1: {e}")
        return None


def _attach_tsa_proof(
    content_digest: ContentDigestProof,
) -> Optional[TsaRfc3161Proof]:
    """Request TSA timestamp for the content digest."""
    # Check if TSA is configured
    tsa_url = os.environ.get("LOOM_TSA_URL")
    if not tsa_url:
        return None

    try:
        from tools.receipts.tsa_client import request_timestamp

        import base64

        digest_bytes = base64.b64decode(content_digest.value)
        token = request_timestamp(digest_bytes, algorithm="sha256")

        if token is None:
            return None

        return TsaRfc3161Proof(
            tsa_id=os.environ.get("LOOM_TSA_ID", "freetsa.org"),
            tsa_time=_now_utc(),
            token=base64.b64encode(token).decode("ascii"),
        )

    except Exception as e:
        logger.warning(f"TSA timestamp failed: {e}")
        return None


def _attach_transparency_log_proof(
    content_digest: ContentDigestProof,
) -> Optional[TransparencyLogProof]:
    """Submit to Rekor transparency log."""
    # Check if Rekor is configured
    if os.environ.get("LOOM_REKOR_ENABLED", "0") != "1":
        return None

    try:
        from receipts.rekor_client import submit_to_rekor

        import base64

        digest_bytes = base64.b64decode(content_digest.value)
        entry = submit_to_rekor(digest_bytes)

        if entry is None:
            return None

        return TransparencyLogProof(
            log_index=entry.get("logIndex", 0),
            entry_uuid=entry.get("uuid", ""),
            integrated_time=datetime.fromtimestamp(
                entry.get("integratedTime", 0), tz=timezone.utc
            ),
            epoch_id=entry.get("epochId", "epoch-0"),
            merkle_path=entry.get("inclusionProof", {}).get("hashes", []),
        )

    except Exception as e:
        logger.warning(f"Rekor submission failed: {e}")
        return None


def _compute_proof_status(
    profile_snapshot,
    proofs: List[Any],
) -> ProofStatusV1:
    """Compute the proof status given required proofs and present proofs."""
    required = set(profile_snapshot.required_proofs)
    present = {p.kind for p in proofs}

    missing_proofs = []
    now = _now_utc()
    for kind in required:
        if kind not in present:
            missing_proofs.append(MissingProof(kind=kind, reason="not_available", recorded_at=now))

    contract_satisfied = len(missing_proofs) == 0

    return ProofStatusV1(
        required=list(required),
        present=list(present),
        missing=missing_proofs,
    )


def build_receipt_v1(
    receipt: "BaseReceipt",
    *,
    profile_id: Optional[str] = None,
    event_time: Optional[datetime] = None,
    include_tsa: bool = True,
    include_rekor: bool = True,
) -> ReceiptV1:
    """
    Build a ReceiptV1 from a legacy BaseReceipt.

    This is the main entry point for creating ReceiptV1 format receipts.
    It handles:
    1. Resolving the proof profile based on domain
    2. Building the envelope with tri-temporal timestamps
    3. Embedding the profile snapshot
    4. Attaching all available proofs
    5. Computing proof status (including degraded mode)

    Args:
        receipt: Legacy receipt to convert
        profile_id: Optional override for the proof profile
        event_time: Optional override for event timestamp
        include_tsa: Whether to request TSA timestamp
        include_rekor: Whether to submit to transparency log

    Returns:
        Complete ReceiptV1 ready for verification and persistence
    """
    # Resolve domain and profile
    domain = _resolve_domain(receipt)
    domain_str = domain.value  # Get string value for profile lookup
    profile = resolve_profile(domain_str, profile_override=profile_id)
    profile_snapshot = get_profile_snapshot(domain_str, profile_override=profile_id)

    # Build envelope
    envelope = _build_envelope(
        receipt,
        attestation_level=profile.attestation_level.value,
        proof_profile_id=profile.profile_id,
        event_time=event_time,
    )

    # Build domain payload
    domain_payload = _build_domain_payload(receipt)

    # Create initial receipt (without proofs) for canonical region
    receipt_v1 = ReceiptV1(
        envelope=envelope,
        profile_snapshot=profile_snapshot,
        proofs=[],
        proof_status=ProofStatusV1(
            required=list(profile_snapshot.required_proofs),
            present=[],
            missing=[],
        ),
        domain_payload=domain_payload,
    )

    # Attach proofs
    proofs = []

    # 1. Content digest (always)
    content_digest = _attach_content_digest(receipt_v1)
    proofs.append(content_digest)

    # 2. Local signature (if signing key available)
    if "local-signature" in profile.required_proofs or "local-signature" in profile.optional_proofs:
        sig_proof = _attach_local_signature(receipt_v1, content_digest)
        if sig_proof:
            proofs.append(sig_proof)

    # 3. TSA timestamp (if configured and requested)
    if include_tsa and ("tsa-rfc3161" in profile.required_proofs or "tsa-rfc3161" in profile.optional_proofs):
        tsa_proof = _attach_tsa_proof(content_digest)
        if tsa_proof:
            proofs.append(tsa_proof)

    # 4. Transparency log (if configured and requested)
    if include_rekor and ("transparency-log" in profile.required_proofs or "transparency-log" in profile.optional_proofs):
        rekor_proof = _attach_transparency_log_proof(content_digest)
        if rekor_proof:
            proofs.append(rekor_proof)

    # Compute final proof status
    proof_status = _compute_proof_status(profile_snapshot, proofs)

    # Return complete receipt
    return ReceiptV1(
        envelope=envelope,
        profile_snapshot=profile_snapshot,
        proofs=proofs,
        proof_status=proof_status,
        domain_payload=domain_payload,
    )


def write_receipt_v1(
    receipt: "BaseReceipt",
    *,
    root: Optional[Path] = None,
    profile_id: Optional[str] = None,
    include_tsa: bool = True,
    include_rekor: bool = True,
) -> ReceiptV1:
    """
    Build and persist a ReceiptV1.

    This function:
    1. Builds the ReceiptV1 from the legacy receipt
    2. Persists to disk in JSON format
    3. Returns the built receipt for further processing

    Args:
        receipt: Legacy receipt to convert and persist
        root: Optional data root override
        profile_id: Optional proof profile override
        include_tsa: Whether to request TSA timestamp
        include_rekor: Whether to submit to transparency log

    Returns:
        The persisted ReceiptV1
    """
    from config.paths import DATA_ROOT
    from receipts.storage import write_bytes_atomic
    from receipts.partitioning import normalise_token, partition_slug

    # Build the ReceiptV1
    receipt_v1 = build_receipt_v1(
        receipt,
        profile_id=profile_id,
        include_tsa=include_tsa,
        include_rekor=include_rekor,
    )

    # Prepare for persistence
    base_path = Path(root or DATA_ROOT)

    # Extract tenant and domain for partitioning
    tenant_value = normalise_token(
        getattr(receipt, "tenant", None) or
        receipt.domain.value if hasattr(receipt.domain, "value") else str(receipt.domain)
    )
    domain_slug = partition_slug(receipt_v1.envelope.domain.value)
    partition_date = receipt_v1.envelope.receipt_time.date().isoformat()

    # Build path
    inbox_dir = base_path / "receipts" / "inbox" / "v1" / tenant_value / domain_slug / partition_date
    inbox_dir.mkdir(parents=True, exist_ok=True)

    # Serialize
    payload = receipt_v1.model_dump(mode="json")
    canonical_bytes = to_jcs_bytes(payload)

    # Write
    json_path = inbox_dir / f"{receipt_v1.envelope.receipt_id}.json"
    write_bytes_atomic(json_path, canonical_bytes, tenant_value)

    logger.info(
        "Wrote ReceiptV1",
        extra={
            "receipt_id": receipt_v1.envelope.receipt_id,
            "domain": receipt_v1.envelope.domain.value,
            "profile": receipt_v1.profile_snapshot.proof_profile_id,
            "contract_satisfied": receipt_v1.proof_status.contract_satisfied,
            "path": str(json_path),
        },
    )

    return receipt_v1
