"""
Rollout Manager - Handles promotion gates and automatic rollback
Tracks: shadow → paper → canary_10pct → full
Evaluates rollback triggers from release_gates.json
"""

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

from ledger import append_ledger_event
from config_snapshot import rollback_snapshot


def _now() -> str:
    return datetime.now(timezone.utc).isoformat()


# ─── Promotion state machine ──────────────────────────────────────────────

PROMOTION_ORDER = ["shadow", "paper", "canary_10pct", "full"]


def _load_release_gates(root: Path) -> Dict[str, Any]:
    path = root / "ops" / "release_gates.json"
    if not path.exists():
        return {}
    try:
        return json.loads(path.read_text(encoding="utf-8"))
    except Exception:
        return {}


def _load_runtime_state(root: Path, tenant_id: str) -> Dict[str, Any]:
    from tenant_manager import load_runtime_state
    return load_runtime_state(root, tenant_id)


def _save_runtime_state(root: Path, tenant_id: str, state: Dict[str, Any]) -> None:
    from tenant_manager import tenant_paths
    paths = tenant_paths(root, tenant_id)
    paths["runtime"].parent.mkdir(parents=True, exist_ok=True)
    paths["runtime"].write_text(
        json.dumps(state, ensure_ascii=False, indent=2),
        encoding="utf-8"
    )


def get_promotion_mode(root: Path, tenant_id: str) -> str:
    state = _load_runtime_state(root, tenant_id)
    return str(state.get("promotion_mode", "shadow"))


def set_promotion_mode(root: Path, tenant_id: str, mode: str) -> Dict[str, Any]:
    """Set promotion mode and log audit event."""
    if mode not in PROMOTION_ORDER:
        raise ValueError(f"Invalid promotion mode: {mode}. Valid: {PROMOTION_ORDER}")

    state = _load_runtime_state(root, tenant_id)
    prev = state.get("promotion_mode", "shadow")
    state["promotion_mode"] = mode
    state["promotion_updated_at"] = _now()
    _save_runtime_state(root, tenant_id, state)

    audit = {
        "promotion_mode": mode,
        "previous_mode": prev,
        "triggered_by": "manual",
        "promotion_updated_at": _now(),
    }
    append_ledger_event(
        root / "logs/chat_tuning_audit.ndjson",
        "promotion_change",
        {"tenant_id": tenant_id, "mode_change": audit},
    )
    return audit


# ─── Gate Evaluation ─────────────────────────────────────────────────────

def _metric_matches(metric: float, operator: str, threshold: float) -> bool:
    if operator == ">":
        return metric > threshold
    if operator == ">=":
        return metric >= threshold
    if operator == "<":
        return metric < threshold
    if operator == "<=":
        return metric <= threshold
    if operator == "==":
        return metric == threshold
    return False


def evaluate_promotion_gates(
    root: Path, tenant_id: str, execution_stats: Dict[str, Any]
) -> List[Dict[str, Any]]:
    """
    Evaluate promotion gates for the current promotion mode.
    Returns list of gates that have been met (ready to promote).
    """
    gates = _load_release_gates(root)
    promotion_gates = gates.get("promotion_gates", {})
    current_mode = get_promotion_mode(root, tenant_id)

    next_idx = None
    for i, mode_name in enumerate(PROMOTION_ORDER):
        if mode_name == current_mode:
            next_idx = i + 1
            break
    if next_idx is None or next_idx >= len(PROMOTION_ORDER):
        return []
    next_mode = PROMOTION_ORDER[next_idx]
    gate_key = f"{current_mode}_to_{next_mode}"
    next_gate = promotion_gates.get(gate_key, {})
    if not isinstance(next_gate, dict) or not next_gate:
        return []

    ready = []
    for req_key, req_val in next_gate.items():
        metric_val = execution_stats.get(req_key, 0)
        operator = "<=" if str(req_key).startswith("max_") else ">="
        if _metric_matches(float(metric_val), operator, float(req_val)):
            ready.append({
                "gate": req_key,
                "required": req_val,
                "current": metric_val,
                "ready": True,
                "next_mode": next_mode,
                "operator": operator,
            })
        else:
            ready.append({
                "gate": req_key,
                "required": req_val,
                "current": metric_val,
                "ready": False,
                "next_mode": next_mode,
                "operator": operator,
            })
    return ready


# ─── Rollback Trigger Evaluation ─────────────────────────────────────────

def evaluate_rollback_triggers(
    root: Path, execution_stats: Dict[str, Any], runtime_state: Dict[str, Any]
) -> List[Dict[str, Any]]:
    """
    Check current metrics against rollback triggers.
    Returns triggered gates.
    """
    gates = _load_release_gates(root)
    triggers = gates.get("rollback_triggers", [])
    if not triggers:
        return []

    triggered = []
    metrics = {**execution_stats, **runtime_state}

    for t in triggers:
        metric_key = t["metric"]
        operator = t["operator"]
        threshold = float(t["threshold"])
        value = float(metrics.get(metric_key, 0))
        if _metric_matches(value, operator, threshold):
            triggered.append({
                "id": t["id"],
                "metric": metric_key,
                "operator": operator,
                "threshold": threshold,
                "current_value": round(value, 4),
                "triggered_at": _now(),
                "trigger_type": "rollback",
            })
    return triggered


def trigger_auto_rollback(
    root: Path, tenant_id: str, trigger: Dict[str, Any]
) -> Dict[str, Any]:
    """
    Execute automatic rollback to previous known-good state.
    Returns rollback result.
    """
    state = _load_runtime_state(root, tenant_id)
    prev_tag = state.get("last_known_good_tag", "")

    rollback_result = {
        "rollback_id": f"auto_{trigger['id']}_{int(datetime.now(timezone.utc).timestamp())}",
        "trigger": trigger,
        "previous_tag": prev_tag,
        "timestamp": _now(),
        "success": False,
        "error": "",
    }

    try:
        if prev_tag:
            result = rollback_snapshot(root, tenant_id, prev_tag)
            rollback_result["success"] = True
            rollback_result["snapshot"] = result
        else:
            # No snapshot to rollback to – activate kill switch
            state["kill_switch"] = True
            state["kill_switch_reason"] = f"rollback_triggered:{trigger['id']}_no_snapshot"
            _save_runtime_state(root, tenant_id, state)
            rollback_result["success"] = True
            rollback_result["kill_switch_activated"] = True
    except Exception as exc:
        rollback_result["error"] = str(exc)
        rollback_result["success"] = False

    # Log audit event
    append_ledger_event(
        root / "logs/chat_tuning_audit.ndjson",
        "auto_rollback",
        {
            "tenant_id": tenant_id,
            "rollback_id": rollback_result["rollback_id"],
            "trigger": trigger,
            "previous_tag": prev_tag,
            "success": rollback_result["success"],
            "error": rollback_result["error"],
        },
    )
    return rollback_result


# ─── Per-decision gate check ─────────────────────────────────────────────

def check_rollout_safety(
    root: Path,
    tenant_id: str,
    execution_stats: Dict[str, Any],
    runtime_state: Dict[str, Any],
) -> Dict[str, Any]:
    """
    Called before each decision. Returns:
      {safe: True}  – proceed normally
      {safe: False, reason: "...", rollback: {...}} – trigger auto-rollback
    """
    promotion_mode = get_promotion_mode(root, tenant_id)

    # Kill switch always blocks new orders
    if runtime_state.get("kill_switch"):
        return {
            "safe": False,
            "reason": "KILL_SWITCH_ACTIVE",
            "action": "BLOCK",
        }

    # Check rollback triggers only for active modes (not shadow)
    if promotion_mode != "shadow":
        triggered = evaluate_rollback_triggers(root, execution_stats, runtime_state)
        if triggered:
            t = triggered[0]
            rollback_result = trigger_auto_rollback(root, tenant_id, t)
            return {
                "safe": False,
                "reason": f"ROLLBACK_TRIGGER:{t['id']}",
                "trigger": t,
                "rollback": rollback_result,
                "action": "AUTO_ROLLBACK",
            }

    return {"safe": True, "promotion_mode": promotion_mode}
