"""Trust scoring and module promotion recommendations for orchestration."""

from __future__ import annotations

import json
import secrets
import sqlite3
from contextlib import contextmanager
from dataclasses import dataclass
from datetime import datetime, timedelta, timezone
from pathlib import Path
from typing import Any, Iterator

from .gate_metrics import compute_gate_metrics
from .modules import ModuleStore, RiskTierConfig, infer_module_write_class_from_tool

_APPROVAL_ACTIONS = {"approve", "approve_http", "reject", "reject_http"}
_REJECT_ACTIONS = {"reject", "reject_http"}
_FAILURE_OUTCOMES = {
    "failed",
    "partial_failure",
    "invariant_failed",
    "budget_exceeded",
    "entity_budget_exhausted",
}

_PROMOTION_MIN_RUNS = 10
_PROMOTION_MIN_SUCCESS_RATE = 0.90
_PROMOTION_MAX_OVERRIDE_RATE = 0.05
_TIGHTEN_MAX_OVERRIDE_RATE = 0.10
_TIGHTEN_MIN_SUCCESS_RATE = 0.80

_RISK_PRESETS: dict[str, dict[str, int]] = {
    "approve_only": {"green_max_score": 0, "yellow_max_score": 95, "red_min_score": 96},
    "conservative": {"green_max_score": 20, "yellow_max_score": 50, "red_min_score": 51},
    "balanced": {"green_max_score": 30, "yellow_max_score": 70, "red_min_score": 71},
    "aggressive": {"green_max_score": 40, "yellow_max_score": 80, "red_min_score": 81},
}


def _utc_now() -> datetime:
    return datetime.now(timezone.utc)


def _parse_iso(value: Any) -> datetime | None:
    if not isinstance(value, str) or not value.strip():
        return None
    raw = value.strip()
    if raw.endswith("Z"):
        raw = raw[:-1] + "+00:00"
    try:
        parsed = datetime.fromisoformat(raw)
    except Exception:
        return None
    if parsed.tzinfo is None:
        parsed = parsed.replace(tzinfo=timezone.utc)
    return parsed.astimezone(timezone.utc)


def _iso(value: datetime) -> str:
    return value.astimezone(timezone.utc).isoformat()


def _safe_int(value: Any, default: int = 0) -> int:
    try:
        return int(value)
    except Exception:
        return default


def _safe_float(value: Any) -> float | None:
    try:
        return float(value)
    except Exception:
        return None


def _round(value: float | None) -> float | None:
    if value is None:
        return None
    return round(float(value), 6)


def _table_exists(conn: sqlite3.Connection, table_name: str) -> bool:
    row = conn.execute(
        """
        SELECT name
        FROM sqlite_master
        WHERE type = 'table' AND name = ?
        LIMIT 1
        """,
        (table_name,),
    ).fetchone()
    return row is not None


@dataclass(frozen=True)
class _ReceiptBundle:
    run_id: str
    receipt: dict[str, Any]
    timestamp: datetime


def _load_receipt_payload(path: Path) -> dict[str, Any] | None:
    if not path.exists():
        return None
    try:
        payload = json.loads(path.read_text(encoding="utf-8"))
    except Exception:
        return None
    return payload if isinstance(payload, dict) else None


def _receipt_timestamp(receipt: dict[str, Any], run_dir: Path) -> datetime:
    for key in ("completed_at", "started_at"):
        parsed = _parse_iso(receipt.get(key))
        if parsed is not None:
            return parsed
    return datetime.fromtimestamp(run_dir.stat().st_mtime, tz=timezone.utc)


def _load_recent_receipts(state_dir: Path, *, window_start: datetime) -> list[_ReceiptBundle]:
    receipts_dir = state_dir / "receipts"
    if not receipts_dir.exists():
        return []
    bundles: list[_ReceiptBundle] = []
    for run_dir in receipts_dir.iterdir():
        if not run_dir.is_dir():
            continue
        receipt = _load_receipt_payload(run_dir / "receipt.json")
        if receipt is None:
            continue
        run_id = str(receipt.get("run_id") or "").strip() or run_dir.name
        timestamp = _receipt_timestamp(receipt, run_dir)
        if timestamp < window_start:
            continue
        bundles.append(_ReceiptBundle(run_id=run_id, receipt=receipt, timestamp=timestamp))
    bundles.sort(key=lambda item: item.timestamp, reverse=True)
    return bundles


def _receipt_module_ids(receipt: dict[str, Any]) -> set[str]:
    module_ids: set[str] = set()
    raw_module_tags = receipt.get("module_tags")
    if isinstance(raw_module_tags, list):
        for raw in raw_module_tags:
            module_id = str(raw or "").strip()
            if module_id:
                module_ids.add(module_id)
    if module_ids:
        return module_ids

    changes = receipt.get("changes")
    if not isinstance(changes, list):
        return set()
    for change in changes:
        if not isinstance(change, dict):
            continue
        tool = str(change.get("tool") or "").strip()
        if not tool:
            continue
        module_id, _write_class = infer_module_write_class_from_tool(tool)
        if module_id:
            module_ids.add(module_id)
    return module_ids


def _receipt_was_auto_executed(receipt: dict[str, Any]) -> bool:
    decision_path = receipt.get("decision_path")
    if not isinstance(decision_path, list):
        return False
    for raw in decision_path:
        value = str(raw or "").strip().lower()
        if "decision:allow:" in value:
            return True
    return False


def _receipt_was_approval_granted(receipt: dict[str, Any]) -> bool:
    decision_path = receipt.get("decision_path")
    if not isinstance(decision_path, list):
        return False
    for raw in decision_path:
        value = str(raw or "").strip().lower()
        if "approval_granted" in value:
            return True
    return False


def _receipt_change_count(receipt: dict[str, Any]) -> int:
    changes = receipt.get("changes")
    if not isinstance(changes, list):
        return 0
    return len([row for row in changes if isinstance(row, dict)])


def _module_override_stats(
    approvals_db_path: Path,
    *,
    run_modules: dict[str, set[str]],
    window_start: datetime,
) -> tuple[dict[str, int], dict[str, int]]:
    decisions: dict[str, int] = {}
    overrides: dict[str, int] = {}
    if not approvals_db_path.exists():
        return decisions, overrides

    with sqlite3.connect(approvals_db_path) as conn:
        conn.row_factory = sqlite3.Row
        if not _table_exists(conn, "approval_tokens"):
            return decisions, overrides
        rows = conn.execute(
            """
            SELECT run_id, metadata_json
            FROM approval_tokens
            WHERE consumed_at IS NOT NULL AND consumed_at >= ?
            """,
            (_iso(window_start),),
        ).fetchall()

    for row in rows:
        run_id = str(row["run_id"] or "").strip()
        if not run_id:
            continue
        modules = run_modules.get(run_id, set())
        if not modules:
            continue
        metadata_json = row["metadata_json"]
        if not isinstance(metadata_json, str) or not metadata_json.strip():
            continue
        try:
            metadata = json.loads(metadata_json)
        except Exception:
            continue
        if not isinstance(metadata, dict):
            continue
        action = str(metadata.get("action") or "").strip().lower()
        if action not in _APPROVAL_ACTIONS:
            continue
        for module_id in modules:
            decisions[module_id] = decisions.get(module_id, 0) + 1
            if action in _REJECT_ACTIONS:
                overrides[module_id] = overrides.get(module_id, 0) + 1
    return decisions, overrides


def _risk_preset_name(risk_tiers: dict[str, Any]) -> str:
    for name, preset in _RISK_PRESETS.items():
        if all(_safe_int(risk_tiers.get(key), -1) == value for key, value in preset.items()):
            return name
    green = _safe_int(risk_tiers.get("green_max_score"), 0)
    if green <= 0:
        return "approve_only"
    if green <= 20:
        return "conservative"
    if green <= 30:
        return "balanced"
    return "aggressive"


def _next_risk_tiers(current: dict[str, Any]) -> dict[str, int]:
    name = _risk_preset_name(current)
    if name == "approve_only":
        return dict(_RISK_PRESETS["conservative"])
    if name == "conservative":
        return dict(_RISK_PRESETS["balanced"])
    if name == "balanced":
        return dict(_RISK_PRESETS["aggressive"])
    return dict(_RISK_PRESETS["aggressive"])


def _tighten_risk_tiers(current: dict[str, Any]) -> dict[str, int]:
    name = _risk_preset_name(current)
    if name == "aggressive":
        return dict(_RISK_PRESETS["balanced"])
    if name == "balanced":
        return dict(_RISK_PRESETS["conservative"])
    return dict(_RISK_PRESETS["approve_only"])


def _module_stage(enabled: bool, auto_runs: int) -> str:
    if not enabled:
        return "observe"
    if auto_runs > 0:
        return "auto_execute"
    return "approve"


def _recommendation(
    *,
    stage: str,
    enabled: bool,
    gate_passed: bool,
    success_rate: float | None,
    override_rate: float | None,
    total_runs: int,
    current_risk_tiers: dict[str, Any],
) -> dict[str, Any]:
    rationale: list[str] = []
    if not enabled:
        rationale.append("module_disabled")
    if not gate_passed:
        rationale.append("gate_metrics_not_passed")
    if total_runs < _PROMOTION_MIN_RUNS:
        rationale.append("insufficient_run_volume")
    if success_rate is None:
        rationale.append("no_success_baseline")
    elif success_rate < _PROMOTION_MIN_SUCCESS_RATE:
        rationale.append("success_rate_below_threshold")
    if override_rate is not None and override_rate > _PROMOTION_MAX_OVERRIDE_RATE:
        rationale.append("override_rate_above_threshold")

    promote_eligible = (
        enabled
        and stage != "auto_execute"
        and gate_passed
        and total_runs >= _PROMOTION_MIN_RUNS
        and (success_rate is not None and success_rate >= _PROMOTION_MIN_SUCCESS_RATE)
        and (override_rate is None or override_rate <= _PROMOTION_MAX_OVERRIDE_RATE)
    )
    if promote_eligible:
        return {
            "action": "promote_to_auto",
            "eligible": True,
            "target_risk_tiers": _next_risk_tiers(current_risk_tiers),
            "rationale": ["promotion_thresholds_met"],
            "thresholds": {
                "min_runs": _PROMOTION_MIN_RUNS,
                "min_success_rate": _PROMOTION_MIN_SUCCESS_RATE,
                "max_override_rate": _PROMOTION_MAX_OVERRIDE_RATE,
            },
        }

    tighten_eligible = (
        enabled
        and stage == "auto_execute"
        and (
            (success_rate is not None and success_rate < _TIGHTEN_MIN_SUCCESS_RATE)
            or (override_rate is not None and override_rate > _TIGHTEN_MAX_OVERRIDE_RATE)
        )
    )
    if tighten_eligible:
        return {
            "action": "tighten_controls",
            "eligible": True,
            "target_risk_tiers": _tighten_risk_tiers(current_risk_tiers),
            "rationale": ["auto_execution_health_degraded"],
            "thresholds": {
                "min_success_rate": _TIGHTEN_MIN_SUCCESS_RATE,
                "max_override_rate": _TIGHTEN_MAX_OVERRIDE_RATE,
            },
        }

    return {
        "action": "hold",
        "eligible": False,
        "target_risk_tiers": None,
        "rationale": rationale or ["healthy_hold"],
        "thresholds": {
            "min_runs": _PROMOTION_MIN_RUNS,
            "min_success_rate": _PROMOTION_MIN_SUCCESS_RATE,
            "max_override_rate": _PROMOTION_MAX_OVERRIDE_RATE,
        },
    }


def compute_orchestration_metrics(
    state_dir: Path | str,
    *,
    lookback_days: int = 30,
    now: datetime | None = None,
) -> dict[str, Any]:
    resolved_state_dir = Path(state_dir).resolve()
    bounded_lookback = max(1, min(int(lookback_days), 3650))
    end_at = now.astimezone(timezone.utc) if now is not None else _utc_now()
    start_at = end_at - timedelta(days=bounded_lookback)

    gate_metrics = compute_gate_metrics(
        resolved_state_dir,
        lookback_days=bounded_lookback,
        now=end_at,
    )
    gate_passed = bool(gate_metrics.get("gate_passed"))

    bundles = _load_recent_receipts(resolved_state_dir, window_start=start_at)
    run_modules: dict[str, set[str]] = {}
    by_module: dict[str, dict[str, Any]] = {}
    for bundle in bundles:
        module_ids = _receipt_module_ids(bundle.receipt)
        if not module_ids:
            continue
        run_modules[bundle.run_id] = module_ids

        outcome = str(bundle.receipt.get("outcome") or "").strip().lower() or "unknown"
        auto_run = _receipt_was_auto_executed(bundle.receipt)
        approval_granted = _receipt_was_approval_granted(bundle.receipt)
        change_count = _receipt_change_count(bundle.receipt)
        for module_id in module_ids:
            current = by_module.setdefault(
                module_id,
                {
                    "total_runs": 0,
                    "success_runs": 0,
                    "failure_runs": 0,
                    "auto_runs": 0,
                    "successful_auto_runs": 0,
                    "approval_granted_runs": 0,
                    "change_count": 0,
                    "latest_run_at": None,
                    "outcome_counts": {},
                },
            )
            current["total_runs"] += 1
            current["change_count"] += change_count
            counts = current["outcome_counts"]
            counts[outcome] = _safe_int(counts.get(outcome), 0) + 1

            if outcome == "success":
                current["success_runs"] += 1
            elif outcome in _FAILURE_OUTCOMES:
                current["failure_runs"] += 1

            if auto_run:
                current["auto_runs"] += 1
                if outcome == "success":
                    current["successful_auto_runs"] += 1
            if approval_granted:
                current["approval_granted_runs"] += 1

            latest = current.get("latest_run_at")
            if latest is None or bundle.timestamp > latest:
                current["latest_run_at"] = bundle.timestamp

    approval_decisions, approval_overrides = _module_override_stats(
        resolved_state_dir / "db" / "approval_tokens.sqlite",
        run_modules=run_modules,
        window_start=start_at,
    )

    module_store = ModuleStore(resolved_state_dir)
    module_rows: list[dict[str, Any]] = []
    promote_candidates = 0
    tighten_candidates = 0

    for envelope in module_store.list_modules():
        counters = by_module.get(
            envelope.module_id,
            {
                "total_runs": 0,
                "success_runs": 0,
                "failure_runs": 0,
                "auto_runs": 0,
                "successful_auto_runs": 0,
                "approval_granted_runs": 0,
                "change_count": 0,
                "latest_run_at": None,
                "outcome_counts": {},
            },
        )
        total_runs = _safe_int(counters.get("total_runs"), 0)
        success_runs = _safe_int(counters.get("success_runs"), 0)
        failure_runs = _safe_int(counters.get("failure_runs"), 0)
        auto_runs = _safe_int(counters.get("auto_runs"), 0)
        successful_auto_runs = _safe_int(counters.get("successful_auto_runs"), 0)
        approvals_used = _safe_int(counters.get("approval_granted_runs"), 0)
        change_count = _safe_int(counters.get("change_count"), 0)
        decision_count = _safe_int(approval_decisions.get(envelope.module_id), 0)
        override_count = _safe_int(approval_overrides.get(envelope.module_id), 0)

        success_rate = (success_runs / total_runs) if total_runs > 0 else None
        override_rate = (override_count / decision_count) if decision_count > 0 else None
        auto_success_rate = (successful_auto_runs / auto_runs) if auto_runs > 0 else None
        trust_denominator = auto_runs + override_count
        trust_score = (
            successful_auto_runs / trust_denominator
            if trust_denominator > 0
            else None
        )

        current_risk_tiers = envelope.risk_tiers.model_dump(exclude_none=False)
        stage = _module_stage(envelope.enabled, auto_runs)
        recommendation = _recommendation(
            stage=stage,
            enabled=envelope.enabled,
            gate_passed=gate_passed,
            success_rate=success_rate,
            override_rate=override_rate,
            total_runs=total_runs,
            current_risk_tiers=current_risk_tiers,
        )
        action = str(recommendation.get("action") or "")
        if action == "promote_to_auto":
            promote_candidates += 1
        elif action == "tighten_controls":
            tighten_candidates += 1

        latest_run_at = counters.get("latest_run_at")
        module_rows.append(
            {
                "module_id": envelope.module_id,
                "display_name": envelope.display_name,
                "enabled": envelope.enabled,
                "status": "active" if envelope.enabled else "disabled",
                "stage": stage,
                "current_risk_tiers": current_risk_tiers,
                "metrics": {
                    "total_runs": total_runs,
                    "success_runs": success_runs,
                    "failure_runs": failure_runs,
                    "auto_runs": auto_runs,
                    "successful_auto_runs": successful_auto_runs,
                    "approval_granted_runs": approvals_used,
                    "change_count": change_count,
                    "override_decisions": decision_count,
                    "override_count": override_count,
                    "success_rate": _round(success_rate),
                    "override_rate": _round(override_rate),
                    "auto_success_rate": _round(auto_success_rate),
                    "trust_score": _round(trust_score),
                    "latest_run_at": _iso(latest_run_at) if isinstance(latest_run_at, datetime) else None,
                    "outcomes": counters.get("outcome_counts", {}),
                },
                "recommendation": recommendation,
            }
        )

    module_rows.sort(key=lambda item: item["module_id"])
    return {
        "generated_at": _iso(end_at),
        "window": {
            "start_at": _iso(start_at),
            "end_at": _iso(end_at),
            "lookback_days": bounded_lookback,
            "receipts_observed": len(bundles),
        },
        "gate_passed": gate_passed,
        "gate_checks": (gate_metrics.get("checks") if isinstance(gate_metrics.get("checks"), dict) else {}),
        "modules": module_rows,
        "summary": {
            "module_count": len(module_rows),
            "promote_candidates": promote_candidates,
            "tighten_candidates": tighten_candidates,
        },
    }


def build_module_promotion_plan(
    state_dir: Path | str,
    module_id: str,
    *,
    action: str,
    lookback_days: int = 30,
    now: datetime | None = None,
) -> dict[str, Any]:
    normalized_module_id = str(module_id or "").strip()
    normalized_action = str(action or "").strip().lower()
    if not normalized_module_id:
        raise ValueError("module_id is required")
    if not normalized_action:
        raise ValueError("action is required")

    metrics = compute_orchestration_metrics(
        state_dir,
        lookback_days=lookback_days,
        now=now,
    )
    modules = metrics.get("modules")
    if not isinstance(modules, list):
        raise ValueError("No module metrics available")
    target_row = None
    for module_row in modules:
        if not isinstance(module_row, dict):
            continue
        if str(module_row.get("module_id") or "") == normalized_module_id:
            target_row = module_row
            break
    if target_row is None:
        raise ValueError(f"Unknown module_id: {normalized_module_id}")

    recommendation = target_row.get("recommendation")
    if not isinstance(recommendation, dict):
        raise ValueError("No recommendation available for module")
    recommended_action = str(recommendation.get("action") or "").strip().lower()
    if recommended_action != normalized_action:
        raise ValueError(
            f"Action '{normalized_action}' not available for {normalized_module_id}. "
            f"Current recommendation is '{recommended_action or 'hold'}'."
        )

    target_risk_tiers = recommendation.get("target_risk_tiers")
    if not isinstance(target_risk_tiers, dict):
        raise ValueError("Recommendation does not include target risk tiers")
    normalized_target = {
        "green_max_score": _safe_int(target_risk_tiers.get("green_max_score"), 0),
        "yellow_max_score": _safe_int(target_risk_tiers.get("yellow_max_score"), 70),
        "red_min_score": _safe_int(target_risk_tiers.get("red_min_score"), 71),
    }
    try:
        risk_config = RiskTierConfig.model_validate(normalized_target)
    except Exception as exc:
        raise ValueError(f"Invalid target risk tier recommendation: {exc}") from exc

    store = ModuleStore(Path(state_dir).resolve())
    current_envelope = store.load_module(normalized_module_id)
    current_payload = current_envelope.model_dump(exclude_none=False)
    current_risk = current_envelope.risk_tiers.model_dump(exclude_none=False)

    if all(
        _safe_int(current_risk.get(key), -1) == value
        for key, value in normalized_target.items()
    ):
        raise ValueError("Module is already at the recommended risk tier")

    proposed_envelope = current_envelope.model_copy(
        update={
            "enabled": True,
            "risk_tiers": risk_config,
        }
    )
    return {
        "module_id": normalized_module_id,
        "action": normalized_action,
        "current_envelope": current_payload,
        "proposed_envelope": proposed_envelope.model_dump(exclude_none=False),
        "current_risk_tiers": current_risk,
        "target_risk_tiers": normalized_target,
        "metrics_snapshot": target_row.get("metrics") if isinstance(target_row.get("metrics"), dict) else {},
        "recommendation": recommendation,
        "gate_passed": bool(metrics.get("gate_passed")),
        "window": metrics.get("window") if isinstance(metrics.get("window"), dict) else {},
    }


class TrustPromotionStore:
    """Persistence for trust promotion recommendations and apply events."""

    def __init__(self, db_path: Path | str):
        self.db_path = Path(db_path).resolve()
        self.db_path.parent.mkdir(parents=True, exist_ok=True)
        self._init_db()

    @contextmanager
    def _connect(self) -> Iterator[sqlite3.Connection]:
        conn = sqlite3.connect(self.db_path)
        conn.row_factory = sqlite3.Row
        conn.execute("PRAGMA journal_mode=WAL")
        conn.execute("PRAGMA busy_timeout=5000")
        try:
            yield conn
            conn.commit()
        except Exception:
            conn.rollback()
            raise
        finally:
            conn.close()

    def _init_db(self) -> None:
        with self._connect() as conn:
            conn.execute(
                """
                CREATE TABLE IF NOT EXISTS trust_promotion_events (
                    event_id TEXT PRIMARY KEY,
                    module_id TEXT NOT NULL,
                    action TEXT NOT NULL,
                    actor TEXT NOT NULL,
                    applied INTEGER NOT NULL,
                    dry_run INTEGER NOT NULL,
                    lookback_days INTEGER NOT NULL,
                    created_at TEXT NOT NULL,
                    payload_json TEXT NOT NULL
                )
                """
            )
            conn.execute(
                """
                CREATE INDEX IF NOT EXISTS idx_trust_promotion_events_module
                ON trust_promotion_events(module_id, created_at DESC)
                """
            )

    def record_event(
        self,
        *,
        module_id: str,
        action: str,
        actor: str,
        applied: bool,
        dry_run: bool,
        lookback_days: int,
        payload: dict[str, Any],
        event_id: str | None = None,
    ) -> str:
        normalized_module_id = str(module_id or "").strip()
        normalized_action = str(action or "").strip().lower()
        normalized_actor = str(actor or "").strip() or "api"
        bounded_lookback = max(1, min(int(lookback_days), 3650))
        if not normalized_module_id:
            raise ValueError("module_id is required")
        if not normalized_action:
            raise ValueError("action is required")

        created_at = _iso(_utc_now())
        resolved_event_id = (
            str(event_id or "").strip()
            or f"{normalized_module_id}:{created_at}:{secrets.token_hex(4)}"
        )
        payload_json = json.dumps(payload, sort_keys=True, ensure_ascii=True)
        with self._connect() as conn:
            conn.execute(
                """
                INSERT INTO trust_promotion_events (
                    event_id,
                    module_id,
                    action,
                    actor,
                    applied,
                    dry_run,
                    lookback_days,
                    created_at,
                    payload_json
                )
                VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
                """,
                (
                    resolved_event_id,
                    normalized_module_id,
                    normalized_action,
                    normalized_actor,
                    1 if applied else 0,
                    1 if dry_run else 0,
                    bounded_lookback,
                    created_at,
                    payload_json,
                ),
            )
        return resolved_event_id

    def list_events(
        self,
        *,
        module_id: str | None = None,
        limit: int = 50,
    ) -> list[dict[str, Any]]:
        bounded_limit = max(1, min(int(limit), 500))
        normalized_module_id = str(module_id or "").strip()
        with self._connect() as conn:
            if normalized_module_id:
                rows = conn.execute(
                    """
                    SELECT *
                    FROM trust_promotion_events
                    WHERE module_id = ?
                    ORDER BY created_at DESC
                    LIMIT ?
                    """,
                    (normalized_module_id, bounded_limit),
                ).fetchall()
            else:
                rows = conn.execute(
                    """
                    SELECT *
                    FROM trust_promotion_events
                    ORDER BY created_at DESC
                    LIMIT ?
                    """,
                    (bounded_limit,),
                ).fetchall()

        events: list[dict[str, Any]] = []
        for row in rows:
            payload_raw = row["payload_json"]
            try:
                payload = json.loads(payload_raw) if isinstance(payload_raw, str) else {}
            except Exception:
                payload = {}
            events.append(
                {
                    "event_id": row["event_id"],
                    "module_id": row["module_id"],
                    "action": row["action"],
                    "actor": row["actor"],
                    "applied": bool(row["applied"]),
                    "dry_run": bool(row["dry_run"]),
                    "lookback_days": _safe_int(row["lookback_days"], 30),
                    "created_at": row["created_at"],
                    "payload": payload if isinstance(payload, dict) else {},
                }
            )
        return events


__all__ = [
    "TrustPromotionStore",
    "build_module_promotion_plan",
    "compute_orchestration_metrics",
]
