"""
Phase 99: SLA Definition & Enforcement Engine.

Define service-level agreements with measurable targets, record metric
measurements, check compliance in real time, and track breach history.
Supports breach-action callbacks for automated remediation.

Metrics: uptime_pct, response_time_ms, task_completion_rate, error_rate.

Storage:
  state/broker/sla/
    definitions.json         — SLA definitions
    measurements.jsonl       — all metric measurements
    breaches.jsonl           — breach records
"""

import json
import time
import uuid
from collections import defaultdict
from datetime import datetime, timezone, timedelta
from pathlib import Path
from typing import Any, Callable, Dict, List, Optional, Tuple

from brynq.server import BROKER_DIR, _write_json_sync, _read_jsonl_sync, _append_jsonl_sync

# ── Paths ──────────────────────────────────────────────────────────────────

SLA_DIR = BROKER_DIR / "sla"
DEFINITIONS_FILE = SLA_DIR / "definitions.json"
MEASUREMENTS_FILE = SLA_DIR / "measurements.jsonl"
BREACHES_FILE = SLA_DIR / "breaches.jsonl"

SLA_DIR.mkdir(parents=True, exist_ok=True)

VALID_METRICS = ("uptime_pct", "response_time_ms", "task_completion_rate", "error_rate")

# Higher-is-better metrics (current >= target means compliant)
HIGHER_IS_BETTER = {"uptime_pct", "task_completion_rate"}
# Lower-is-better metrics (current <= target means compliant)
LOWER_IS_BETTER = {"response_time_ms", "error_rate"}

# ── In-memory breach actions ──────────────────────────────────────────────

_breach_actions: Dict[str, Callable] = {}


# ── Helpers ────────────────────────────────────────────────────────────────

def _load_definitions() -> dict:
    if not DEFINITIONS_FILE.exists():
        return {}
    try:
        return json.loads(DEFINITIONS_FILE.read_text("utf-8"))
    except (json.JSONDecodeError, OSError):
        return {}


def _save_definitions(defs: dict) -> None:
    _write_json_sync(DEFINITIONS_FILE, defs)


def _is_compliant(metric: str, current: float, target: float) -> bool:
    if metric in HIGHER_IS_BETTER:
        return current >= target
    return current <= target


# ── Public API ─────────────────────────────────────────────────────────────

def define_sla(name: str, metric: str, target: float,
               measurement_window_hours: int = 24) -> dict:
    """Define a new SLA. Metric must be one of the valid metrics."""
    if metric not in VALID_METRICS:
        raise ValueError(f"Invalid metric '{metric}'. Must be one of {VALID_METRICS}")
    defs = _load_definitions()
    sla = {
        "name": name,
        "metric": metric,
        "target": target,
        "measurement_window_hours": measurement_window_hours,
        "created_at": datetime.now(timezone.utc).isoformat(),
    }
    defs[name] = sla
    _save_definitions(defs)
    return sla


def record_measurement(sla_name: str, value: float) -> dict:
    """Record a metric measurement for an SLA."""
    defs = _load_definitions()
    if sla_name not in defs:
        raise KeyError(f"SLA '{sla_name}' not defined")
    now = datetime.now(timezone.utc).isoformat()
    entry = {
        "measurement_id": uuid.uuid4().hex[:12],
        "sla_name": sla_name,
        "metric": defs[sla_name]["metric"],
        "value": value,
        "timestamp": now,
        "epoch": time.time(),
    }
    _append_jsonl_sync(MEASUREMENTS_FILE, entry)
    # Check for breach
    compliant, current, target, gap = check_compliance(sla_name)
    if not compliant:
        breach = {
            "breach_id": uuid.uuid4().hex[:12],
            "sla_name": sla_name,
            "metric": defs[sla_name]["metric"],
            "target": target,
            "actual": current,
            "gap": gap,
            "timestamp": now,
        }
        _append_jsonl_sync(BREACHES_FILE, breach)
        # Fire breach action if registered
        if sla_name in _breach_actions:
            try:
                _breach_actions[sla_name](breach)
            except Exception:
                pass
    return entry


def check_compliance(sla_name: str) -> Tuple[bool, float, float, float]:
    """Returns (compliant, current_value, target, gap)."""
    defs = _load_definitions()
    if sla_name not in defs:
        raise KeyError(f"SLA '{sla_name}' not defined")
    sla = defs[sla_name]
    metric = sla["metric"]
    target = sla["target"]
    window = sla.get("measurement_window_hours", 24)
    cutoff = (datetime.now(timezone.utc) - timedelta(hours=window)).isoformat()
    measurements = _read_jsonl_sync(MEASUREMENTS_FILE)
    recent = [
        m for m in measurements
        if m.get("sla_name") == sla_name and m.get("timestamp", "") >= cutoff
    ]
    if not recent:
        # No data — assume compliant
        return (True, target, target, 0.0)
    current = sum(m["value"] for m in recent) / len(recent)
    current = round(current, 4)
    compliant = _is_compliant(metric, current, target)
    gap = round(abs(current - target), 4)
    return (compliant, current, target, gap)


def get_sla_report(hours: int = 24) -> List[dict]:
    """All SLAs with compliance status for the given window."""
    defs = _load_definitions()
    report = []
    for name, sla in defs.items():
        compliant, current, target, gap = check_compliance(name)
        report.append({
            "name": name,
            "metric": sla["metric"],
            "target": target,
            "current": current,
            "compliant": compliant,
            "gap": gap,
        })
    return report


def list_slas() -> List[dict]:
    """Return all SLA definitions."""
    defs = _load_definitions()
    return list(defs.values())


def get_sla(name: str) -> dict:
    """Get a single SLA definition."""
    defs = _load_definitions()
    if name not in defs:
        raise KeyError(f"SLA '{name}' not defined")
    return defs[name]


def delete_sla(name: str) -> dict:
    """Delete an SLA definition."""
    defs = _load_definitions()
    if name not in defs:
        raise KeyError(f"SLA '{name}' not defined")
    removed = defs.pop(name)
    _save_definitions(defs)
    _breach_actions.pop(name, None)
    return removed


def get_breach_history(sla_name: Optional[str] = None) -> List[dict]:
    """Return past SLA breaches, optionally filtered by SLA name."""
    breaches = _read_jsonl_sync(BREACHES_FILE)
    if sla_name:
        breaches = [b for b in breaches if b.get("sla_name") == sla_name]
    return breaches


def set_breach_action(sla_name: str, action_fn: Callable) -> None:
    """Register a callback to fire when an SLA is breached."""
    defs = _load_definitions()
    if sla_name not in defs:
        raise KeyError(f"SLA '{sla_name}' not defined")
    _breach_actions[sla_name] = action_fn


def reset_engine() -> None:
    """Reset in-memory state (for testing)."""
    _breach_actions.clear()
