"""
InterruptionHandler — Graceful stop on governance signals.

When NUMA (or any governance layer) sends a kill signal mid-run:
  1. Current step completes and seals (never leave a step half-done)
  2. Interruption proof is sealed with the signal reference
  3. Slot closes early with interruption proof in the Merkle root
  4. Evidence chain records: "run X interrupted at step N by signal Y"

The handler is injected into the PGDAGRunner.  Between each step,
the runner checks `is_triggered`.  If true, it seals an interruption
proof and breaks the step loop.
"""

from __future__ import annotations

import asyncio
import logging
from dataclasses import dataclass, field
from datetime import datetime, timezone
from typing import Any, Dict, Optional

from .artifact import ProofArtifact, ProofStatus

logger = logging.getLogger(__name__)


# ---------------------------------------------------------------------------
# Interruption signal
# ---------------------------------------------------------------------------

@dataclass
class InterruptionSignal:
    """Signal from a governance layer requesting a graceful stop.

    signal_type:  "numa_freeze" | "compliance_hold" | "operator_kill" | "throttle" | etc.
    signal_hash:  SnapChore hash of the originating signal (for proof linkage).
    source:       Who sent it: "numa" | "reflex" | "operator" | "compliance".
    reason:       Human-readable explanation.
    """
    signal_type: str
    signal_hash: str = ""
    source: str = ""
    reason: str = ""
    received_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))

    def to_dict(self) -> Dict[str, Any]:
        return {
            "signal_type": self.signal_type,
            "signal_hash": self.signal_hash,
            "source": self.source,
            "reason": self.reason,
            "received_at": self.received_at.isoformat(),
        }


# ---------------------------------------------------------------------------
# InterruptionHandler
# ---------------------------------------------------------------------------

class InterruptionHandler:
    """Manages graceful interruption of PG-DAG runs.

    Usage:
        handler = InterruptionHandler()

        # Inject signal from governance layer
        handler.trigger(InterruptionSignal(
            signal_type="numa_freeze",
            signal_hash="abc123",
            source="numa",
            reason="Regime change detected",
        ))

        # In the DAG runner loop:
        if handler.is_triggered:
            proof = handler.build_interruption_proof(
                last_completed_step="reserve_float",
                run_id="run-123",
                spec_version="1.0",
            )
            # ... close slot early ...
    """

    def __init__(self) -> None:
        self._signal: Optional[InterruptionSignal] = None
        self._event = asyncio.Event()

    @property
    def is_triggered(self) -> bool:
        """Whether a governance signal has been injected."""
        return self._signal is not None

    @property
    def signal(self) -> Optional[InterruptionSignal]:
        """The injected signal, if any."""
        return self._signal

    def trigger(self, signal: InterruptionSignal) -> None:
        """Inject a governance signal.  Thread-safe via asyncio.Event."""
        self._signal = signal
        self._event.set()
        logger.warning(
            "Interruption triggered: %s from %s — %s",
            signal.signal_type,
            signal.source,
            signal.reason,
        )

    def reset(self) -> None:
        """Clear the signal (e.g. between runs)."""
        self._signal = None
        self._event.clear()

    async def wait(self, timeout: Optional[float] = None) -> bool:
        """Wait for an interruption signal.  Returns True if triggered."""
        try:
            await asyncio.wait_for(self._event.wait(), timeout=timeout)
            return True
        except asyncio.TimeoutError:
            return False

    def build_interruption_proof(
        self,
        last_completed_step: str,
        run_id: str,
        spec_version: str,
    ) -> ProofArtifact:
        """Build a proof artifact recording the interruption.

        This proof goes into the Merkle root alongside step proofs,
        so the interruption is part of the cryptographic record.
        """
        signal_data = self._signal.to_dict() if self._signal else {}
        return ProofArtifact(
            step_id="interrupted",
            spec_version=spec_version,
            inputs_fingerprint=f"interrupt:{run_id}:{last_completed_step}",
            outputs_fingerprint="",
            proof_hash=signal_data.get("signal_hash", ""),
            block_ref="",
            status=ProofStatus.INTERRUPTED,
            metrics={
                "last_completed_step": last_completed_step,
                "run_id": run_id,
                "signal": signal_data,
            },
        )
