"""
Metrics Aggregation Module
Aggregates per-tenant execution statistics from:
  - logs/mt5_exec_stats.{date}.ndjson  (execution results)
  - logs/decision_ledger.ndjson          (decision outcomes)
  - ops/runtime_state.json                (runtime state)
"""

import json
import re
from collections import defaultdict
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Dict, List, Optional

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

def _load_ndjson(path: Path) -> List[Dict]:
    if not path.exists():
        return []
    records = []
    try:
        with path.open(encoding="utf-8") as f:
            for line in f:
                line = line.strip()
                if not line:
                    continue
                try:
                    records.append(json.loads(line))
                except Exception:
                    continue
    except Exception:
        pass
    return records


def _ts_of(record: Dict) -> Optional[datetime]:
    ts_str = str(record.get("timestamp", record.get("ts", "")))
    if not ts_str:
        return None
    try:
        ts_str = ts_str.replace("Z", "+00:00")
        return datetime.fromisoformat(ts_str).astimezone(timezone.utc)
    except Exception:
        return None


def _today_file(log_dir: Path, prefix: str, suffix: str) -> Path:
    today = datetime.now(timezone.utc).strftime("%Y%m%d")
    return log_dir / f"{prefix}.{today}.{suffix}"


# ─── MT5 Execution Stats ─────────────────────────────────────────────────

def aggregate_exec_stats(log_dir: Path) -> Dict[str, Any]:
    """
    Reads today's mt5_exec_stats.{date}.ndjson and returns aggregated counters.
    """
    path = _today_file(log_dir, "mt5_exec_stats", "ndjson")
    records = _load_ndjson(path)

    totals = defaultdict(int)
    latencies = []
    retry_count = 0
    init_fail = 0

    for r in records:
        status = str(r.get("status", ""))
        totals[status] += 1
        if "latencies_ms" in r:
            latencies.extend(r["latencies_ms"])
        retry_count += int(r.get("retries", 0))
        if status == "INIT_FAILED":
            init_fail += 1

    total = sum(totals.values())
    return {
        "exec_total": total,
        "exec_FILLED": totals.get("FILLED", 0),
        "exec_REJECTED": totals.get("REJECTED", 0),
        "exec_TIMEOUT": totals.get("TIMEOUT", 0),
        "exec_RETRY_EXHAUSTED": totals.get("RETRY_EXHAUSTED", 0),
        "exec_INIT_FAILED": init_fail,
        "exec_success_rate_pct": round(
            totals.get("FILLED", 0) / max(total, 1) * 100, 2
        ),
        "exec_reject_rate_pct": round(
            (totals.get("REJECTED", 0) + totals.get("RETRY_EXHAUSTED", 0))
            / max(total, 1) * 100, 2
        ),
        "exec_timeout_rate_pct": round(
            totals.get("TIMEOUT", 0) / max(total, 1) * 100, 2
        ),
        "exec_total_retries": retry_count,
        "exec_avg_latency_ms": round(sum(latencies) / max(len(latencies), 1), 1),
        "exec_p95_latency_ms": _pct(latencies, 95),
        "exec_p50_latency_ms": _pct(latencies, 50),
    }


def _pct(data: List[float], p: float) -> float:
    if not data:
        return 0.0
    s = sorted(data)
    idx = int((len(s) - 1) * p / 100.0)
    return round(float(s[min(idx, len(s) - 1)]), 1)


# ─── Decision Ledger Stats ─────────────────────────────────────────────────

def aggregate_ledger_stats(log_dir: Path) -> Dict[str, Any]:
    """
    Reads decision_ledger.ndjson and aggregates decision outcomes.
    """
    path = log_dir / "decision_ledger.ndjson"
    records = _load_ndjson(path)

    now = datetime.now(timezone.utc)
    window_15m = now.timestamp() - 15 * 60
    window_1h = now.timestamp() - 60 * 60
    window_1d = now.timestamp() - 24 * 60 * 60

    counters = {"total_1d": 0, "total_1h": 0, "total_15m": 0,
                "rejected_1d": 0, "rejected_1h": 0, "rejected_15m": 0,
                "approved_1d": 0, "approved_1h": 0, "approved_15m": 0,
                "clamped_1d": 0, "clamped_1h": 0, "clamped_15m": 0,
                "errors_1d": 0}

    for r in records:
        ts = _ts_of(r)
        if ts is None:
            continue
        t = ts.timestamp()
        if t < window_1d:
            continue
        counters["total_1d"] += 1
        if t >= window_1h:
            counters["total_1h"] += 1
        if t >= window_15m:
            counters["total_15m"] += 1

        sig = r.get("payload", {}).get("signal", {})
        gov = str(sig.get("governor_status", ""))

        if gov == "REJECTED":
            counters["rejected_1d"] += 1
            if t >= window_1h: counters["rejected_1h"] += 1
            if t >= window_15m: counters["rejected_15m"] += 1
        elif gov == "APPROVED":
            counters["approved_1d"] += 1
            if t >= window_1h: counters["approved_1h"] += 1
            if t >= window_15m: counters["approved_15m"] += 1
        elif gov == "CLAMPED":
            counters["clamped_1d"] += 1
            if t >= window_1h: counters["clamped_1h"] += 1
            if t >= window_15m: counters["clamped_15m"] += 1

        cat = str(r.get("category", ""))
        if cat in {"kill_switch", "error", "schema_validation_failed"}:
            counters["errors_1d"] += 1

    return {
        "decision_total_1d": counters["total_1d"],
        "decision_total_1h": counters["total_1h"],
        "decision_total_15m": counters["total_15m"],
        "decision_approved_1d": counters["approved_1d"],
        "decision_approved_1h": counters["approved_1h"],
        "decision_approved_15m": counters["approved_15m"],
        "decision_rejected_1d": counters["rejected_1d"],
        "decision_rejected_1h": counters["rejected_1h"],
        "decision_rejected_15m": counters["rejected_15m"],
        "decision_clamped_1d": counters["clamped_1d"],
        "decision_clamped_1h": counters["clamped_1h"],
        "decision_clamped_15m": counters["clamped_15m"],
        "decision_error_rate_pct_15m": round(
            counters["rejected_15m"] / max(counters["total_15m"], 1) * 100, 2
        ),
        "decision_error_rate_pct_1h": round(
            counters["rejected_1h"] / max(counters["total_1h"], 1) * 100, 2
        ),
        "decision_errors_1d": counters["errors_1d"],
        "decision_approval_rate_pct_1d": round(
            counters["approved_1d"] / max(counters["total_1d"], 1) * 100, 2
        ),
    }


# ─── Runtime State Stats ──────────────────────────────────────────────────

def aggregate_runtime_state(project_root: Path, tenant_id: str) -> Dict[str, Any]:
    """Reads runtime_state.json for a tenant."""
    from tenant_manager import load_runtime_state
    state = load_runtime_state(project_root, tenant_id)
    return {
        "kill_switch": bool(state.get("kill_switch", False)),
        "runtime_state": {k: v for k, v in state.items()
                          if k not in {"_metadata"}},
        "intraday_drawdown_pct": float(state.get("intraday_drawdown_pct", 0)),
        "consecutive_losses": int(state.get("consecutive_losses", 0)),
        "daily_loss_pct": float(state.get("daily_loss_pct", 0)),
        "ack_timeout_count_5m": int(state.get("ack_timeout_count_5m", 0)),
    }


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

def evaluate_rollback_gates(metrics: Dict[str, Any]) -> List[Dict[str, Any]]:
    """
    Checks current metrics against rollback triggers from release_gates.json.
    Returns list of triggered gates.
    """
    import importlib.util
    gates_path = Path(__file__).resolve().parents[1] / "ops" / "release_gates.json"
    if not gates_path.exists():
        return []
    gates = json.loads(gates_path.read_text(encoding="utf-8"))
    triggered = []
    for trigger in gates.get("rollback_triggers", []):
        metric_key = trigger["metric"]
        operator = trigger["operator"]
        threshold = float(trigger["threshold"])
        value = float(metrics.get(metric_key, 0))
        if _apply_op(value, operator, threshold):
            triggered.append({
                "id": trigger["id"],
                "metric": metric_key,
                "operator": operator,
                "threshold": threshold,
                "current_value": value,
                "triggered_at": datetime.now(timezone.utc).isoformat(),
            })
    return triggered


def _apply_op(value: float, op: str, threshold: float) -> bool:
    if op == ">":
        return value > threshold
    if op == ">=":
        return value >= threshold
    if op == "<":
        return value < threshold
    if op == "<=":
        return value <= threshold
    if op == "==":
        return value == threshold
    return False


# ─── Full Tenant Metrics ──────────────────────────────────────────────────

def build_tenant_metrics(
    project_root: Path,
    tenant_id: str,
) -> Dict[str, Any]:
    log_dir = project_root / "logs"
    return {
        "tenant_id": tenant_id or "default",
        "generated_at": datetime.now(timezone.utc).isoformat(),
        "window_15m": {
            "total_signals": aggregate_ledger_stats(log_dir).get("decision_total_15m", 0),
            "error_rate_pct": aggregate_ledger_stats(log_dir).get("decision_error_rate_pct_15m", 0),
        },
        "window_1h": {
            "total_signals": aggregate_ledger_stats(log_dir).get("decision_total_1h", 0),
            "approval_rate_pct": aggregate_ledger_stats(log_dir).get("decision_approval_rate_pct_1d", 0),
        },
        "window_1d": {
            "total_signals": aggregate_ledger_stats(log_dir).get("decision_total_1d", 0),
        },
        "execution": aggregate_exec_stats(log_dir),
        "runtime": aggregate_runtime_state(project_root, tenant_id),
    }
