from datetime import datetime, timezone


DEFAULT_ROUTE_CLASSES = ("localize", "tutor", "hardware_explainer", "intent_compile")
QUALITY_GOOD_STATUSES = {"ok", "cached", "reused_last_good"}


def build_model_routing_recommendation(events, route_classes=None, min_samples=2):
    routes = [str(item).strip().lower() for item in (route_classes or DEFAULT_ROUTE_CLASSES) if str(item).strip()]
    grouped = {route: {} for route in routes}

    for event in list(events or []):
        route = _route_class_for_event(event)
        if route not in grouped:
            continue
        model_meta = _model_meta(event.get("model"))
        model_key = (model_meta["provider"], model_meta["name"])
        bucket = grouped[route].setdefault(
            model_key,
            {"model": model_meta, "latencies": [], "good": 0, "failed": 0, "total": 0},
        )
        latency_ms = _to_int(((event.get("timing") or {}).get("latency_ms")))
        if latency_ms is not None and latency_ms >= 0:
            bucket["latencies"].append(latency_ms)
        quality_status = str(((event.get("payload") or {}).get("quality_status") or "")).strip().lower()
        if quality_status in QUALITY_GOOD_STATUSES:
            bucket["good"] += 1
        outcome = str(event.get("outcome") or "").strip().lower()
        if outcome in {"failed", "denied"}:
            bucket["failed"] += 1
        bucket["total"] += 1

    recommendations = []
    min_count = max(1, int(min_samples or 1))
    for route in routes:
        candidates = []
        for model_key, stats in grouped.get(route, {}).items():
            total = int(stats.get("total") or 0)
            if total < min_count:
                continue
            latencies = list(stats.get("latencies") or [])
            latencies.sort()
            quality_rate = float(stats.get("good") or 0) / float(max(1, total))
            failure_rate = float(stats.get("failed") or 0) / float(max(1, total))
            candidate = {
                "model": dict(stats.get("model") or {}),
                "sample_count": total,
                "quality_good_rate": round(quality_rate, 4),
                "failure_rate": round(failure_rate, 4),
                "latency_ms": {
                    "avg": _avg_int(latencies),
                    "p50": _percentile(latencies, 50),
                    "p95": _percentile(latencies, 95),
                },
            }
            candidates.append(candidate)
        candidates.sort(key=_candidate_sort_key)
        winner = candidates[0]["model"] if candidates else None
        recommendations.append(
            {
                "route_class": route,
                "winner": winner,
                "candidates": candidates,
            }
        )

    return {
        "generated_at": datetime.now(timezone.utc).isoformat(),
        "routes_evaluated": routes,
        "min_samples": min_count,
        "recommendations": recommendations,
    }


def _route_class_for_event(event):
    payload = event.get("payload") or {}
    event_type = str(event.get("event_type") or "").strip().lower()
    if event_type == "terminal_output" and bool(payload.get("derived")):
        module = str(payload.get("module") or "").strip().lower()
        if module in {"localize", "tutor", "hardware_explainer"}:
            return module
    if event_type == "ai_output":
        stage = str(payload.get("intent_stage") or "").strip().lower()
        if stage in {"intent_compiled", "intent_received", "intent_authorized", "intent_executed"}:
            return "intent_compile"
    return None


def _model_meta(model):
    meta = dict(model or {})
    provider = str(meta.get("provider") or "unknown").strip().lower() or "unknown"
    name = str(meta.get("name") or "unknown").strip() or "unknown"
    return {"provider": provider, "name": name}


def _to_int(value):
    try:
        return int(value)
    except Exception:
        return None


def _avg_int(items):
    if not items:
        return None
    return int(sum(items) / float(len(items)))


def _percentile(items, pct):
    values = list(items or [])
    if not values:
        return None
    values.sort()
    rank = (float(max(0, min(100, pct))) / 100.0) * float(len(values) - 1)
    lower = int(rank)
    upper = min(lower + 1, len(values) - 1)
    if lower == upper:
        return int(values[lower])
    frac = rank - float(lower)
    return int(values[lower] + (values[upper] - values[lower]) * frac)


def _candidate_sort_key(candidate):
    model = candidate.get("model") or {}
    provider = str(model.get("provider") or "")
    name = str(model.get("name") or "")
    p95 = candidate.get("latency_ms", {}).get("p95")
    p50 = candidate.get("latency_ms", {}).get("p50")
    return (
        -float(candidate.get("quality_good_rate") or 0.0),
        float(candidate.get("failure_rate") or 0.0),
        float(p95 if p95 is not None else 10**9),
        float(p50 if p50 is not None else 10**9),
        -int(candidate.get("sample_count") or 0),
        provider,
        name,
    )
