from __future__ import annotations

import hashlib
import json
from dataclasses import dataclass, field
from enum import Enum
from typing import Any, Dict, Tuple


class InterventionKind(str, Enum):
    """Intervention kinds with stable semantics (internal contract)."""

    GROUNDING_INJECTION = "grounding_injection"
    STABILITY_INJECTION = "stability_injection"
    CONTEXT_PRUNE_SAFE = "context_prune_safe"
    CONSENSUS = "consensus"
    AUTO_HEAL_ROLLBACK = "auto_heal_rollback"


@dataclass(frozen=True)
class InterventionSpec:
    """A request to apply an intervention.

    Idempotency:
    - The same `idempotency_key` must have no additional side effects if applied twice.
    """

    kind: InterventionKind
    reason: str
    params: Dict[str, Any] = field(default_factory=dict)
    idempotency_key: str = ""
    version: int = 1


@dataclass(frozen=True)
class InterventionOutcome:
    applied: bool
    idempotency_key: str
    mutated_fields: Tuple[str, ...] = ()
    notes: str = ""
    evidence: Dict[str, Any] = field(default_factory=dict)


def make_idempotency_key(
    kind: InterventionKind, *, scope: str, reason: str, params: Dict[str, Any]
) -> str:
    """Compute a stable idempotency key.

    `scope` should be stable for the thing you're protecting (request_id, agent_id, etc.).
    """
    payload = {
        "kind": str(kind.value),
        "scope": str(scope),
        "reason": str(reason),
        "params": params or {},
        "v": 1,
    }
    raw = json.dumps(payload, sort_keys=True, separators=(",", ":"), ensure_ascii=True).encode(
        "utf-8"
    )
    return hashlib.sha256(raw).hexdigest()[:16]


def ensure_entropic_meta(request: Dict[str, Any]) -> Dict[str, Any]:
    """Ensure the request has an entropic metadata bag.

    Shape:
    - request["_entropic"]["applied"] : list[str]
    """
    meta = request.get("_entropic")
    if not isinstance(meta, dict):
        meta = {}
        request["_entropic"] = meta
    applied = meta.get("applied")
    if not isinstance(applied, list):
        meta["applied"] = []
    return request


def already_applied(request: Dict[str, Any], key: str) -> bool:
    meta = request.get("_entropic")
    if not isinstance(meta, dict):
        return False
    applied = meta.get("applied")
    if not isinstance(applied, list):
        return False
    return key in applied


def mark_applied(request: Dict[str, Any], key: str) -> None:
    ensure_entropic_meta(request)
    applied = request["_entropic"]["applied"]
    if key not in applied:
        applied.append(key)
