"""Dashboard data processing utilities for Mission Control monitoring.

This module provides pure utility functions to transform raw monitoring data
into dashboard-ready formats. These functions are pure (no I/O, no RPC calls)
and are called by API endpoints to transform database-stored data.
"""

from __future__ import annotations

import math
from collections import defaultdict


def ModelName(model: str) -> str:
    """Map raw provider/model strings to display names.
    
    Ported from Go's apprefresh.ModelName().
    Example: "anthropic/claude-opus-4-6" -> "Claude Opus 4.6"
    """
    ml = model.lower()
    if "/" in ml:
        parts = ml.split("/", 2)
        ml = parts[1]
    
    if "opus-4-6" in ml:
        return "Claude Opus 4.6"
    if "opus" in ml:
        return "Claude Opus 4.5"
    if "sonnet" in ml:
        return "Claude Sonnet"
    if "haiku" in ml:
        return "Claude Haiku"
    if "grok-4-fast" in ml:
        return "Grok 4 Fast"
    if "grok-4" in ml or "grok4" in ml:
        return "Grok 4"
    if "gemini-2.5-pro" in ml or "gemini-pro" in ml:
        return "Gemini 2.5 Pro"
    if "gemini-3-flash" in ml:
        return "Gemini 3 Flash"
    if "gemini-2.5-flash" in ml:
        return "Gemini 2.5 Flash"
    if "gemini" in ml or "flash" in ml:
        return "Gemini Flash"
    if "minimax-m2.5" in ml:
        return "MiniMax M2.5"
    if "minimax-m2" in ml or "minimax" in ml:
        return "MiniMax"
    if "glm-5" in ml:
        return "GLM-5"
    if "glm-4" in ml:
        return "GLM-4"
    if "k2p5" in ml or "kimi" in ml:
        return "Kimi K2.5"
    if "gpt-5.3-codex" in ml:
        return "GPT-5.3 Codex"
    if "gpt-5" in ml:
        return "GPT-5"
    if "gpt-4o" in ml:
        return "GPT-4o"
    if "gpt-4" in ml:
        return "GPT-4"
    if "o1" in ml:
        return "O1"
    if "o3" in ml:
        return "O3"
    return model


def TitleCase(s: str) -> str:
    """Uppercase the first character of an ASCII string.
    
    Ported from Go's apprefresh.TitleCase().
    """
    if not s:
        return s
    first = s[0]
    if 'a' <= first <= 'z':
        return chr(ord(first) - 32) + s[1:]
    return s


def round2(v: float) -> float:
    """Round to 2 decimal places."""
    return round(v * 100) / 100


def sum_bucket_costs(buckets: dict[str, dict]) -> float:
    """Sum the `cost` field from a dict of TokenBucket-like dicts.
    
    Args:
        buckets: Dict mapping model names to TokenBucket-like dicts with 'cost' field
        
    Returns:
        Total cost from all buckets
    """
    total = 0.0
    for bucket in buckets.values():
        if isinstance(bucket, dict):
            total += bucket.get("cost", 0.0)
    return total


def BuildDailyChart(
    daily_costs: dict[str, dict[str, float]],
    daily_tokens: dict[str, dict[str, int]],
    daily_calls: dict[str, dict[str, int]],
    daily_subagent_costs: dict[str, float],
    daily_subagent_count: dict[str, int],
    days: int = 30
) -> list[dict]:
    """Aggregate cost/token/call data into daily buckets for chart rendering.
    
    Ported from Go's apprefresh.BuildDailyChart().
    
    Args:
        daily_costs: Dict of {date: {model: cost}}
        daily_tokens: Dict of {date: {model: tokens}}
        daily_calls: Dict of {date: {model: calls}}
        daily_subagent_costs: Dict of {date: subagent_cost}
        daily_subagent_count: Dict of {date: subagent_count}
        days: Number of days to include (default 30)
        
    Returns:
        List of dicts with keys: date, label, total, tokens, calls,
        subagent_cost, subagent_runs, models
    """
    from datetime import date, timedelta
    
    # Generate last N days
    today = date.today()
    chart_dates = []
    for i in range(days - 1, -1, -1):
        chart_dates.append((today - timedelta(days=i)).strftime("%Y-%m-%d"))
    
    # Find top 6 models by cost
    model_totals: dict[str, float] = defaultdict(float)
    for d in chart_dates:
        if d in daily_costs:
            for m, c in daily_costs[d].items():
                model_totals[m] += c
    
    # Sort models by total cost descending
    sorted_models = sorted(model_totals.items(), key=lambda x: x[1], reverse=True)
    top_models = {m for m, _ in sorted_models[:6]}
    
    chart = []
    for d in chart_dates:
        day_costs = daily_costs.get(d, {})
        day_tokens = daily_tokens.get(d, {})
        day_calls = daily_calls.get(d, {})
        
        total_cost = sum(day_costs.values())
        total_tokens = sum(day_tokens.values())
        total_calls = sum(day_calls.values())
        
        models = {}
        other_cost = 0.0
        for m, c in day_costs.items():
            if m in top_models:
                models[m] = round2(c)
            else:
                other_cost += c
        
        if other_cost > 0:
            models["Other"] = round2(other_cost)
        
        chart.append({
            "date": d,
            "label": d[5:],  # MM-DD format
            "total": round2(total_cost),
            "tokens": total_tokens,
            "calls": total_calls,
            "subagent_cost": round2(daily_subagent_costs.get(d, 0.0)),
            "subagent_runs": daily_subagent_count.get(d, 0),
            "models": models,
        })
    
    return chart


def BuildAlerts(
    total_cost_today: float,
    cost_high: float,
    cost_warn: float,
    crons: list[dict],
    sessions: list[dict],
    context_threshold: float,
    gateway_status: dict,
    memory_threshold_kb: int
) -> list[dict]:
    """Evaluate alert conditions and return list of alerts.
    
    Ported from Go's apprefresh.BuildAlerts().
    
    Args:
        total_cost_today: Current day's total cost
        cost_high: High cost threshold
        cost_warn: Warning cost threshold
        crons: List of cron job status dicts with 'lastStatus' field
        sessions: List of session dicts with 'contextPct' field
        context_threshold: Context usage percentage threshold
        gateway_status: Gateway health dict with 'status' and 'rss' fields
        memory_threshold_kb: Memory threshold in KB
        
    Returns:
        List of alert dicts with keys: type, icon, message, severity
    """
    alerts = []
    
    # Cost alerts
    if total_cost_today > cost_high:
        alerts.append({
            "type": "warning",
            "icon": "💰",
            "message": f"High daily cost: ${total_cost_today:.2f}",
            "severity": "high",
        })
    elif total_cost_today > cost_warn:
        alerts.append({
            "type": "info",
            "icon": "💵",
            "message": f"Daily cost above ${cost_warn:.0f}: ${total_cost_today:.2f}",
            "severity": "medium",
        })
    
    # Cron alerts
    for cron in crons:
        if cron.get("lastStatus") == "error":
            name = cron.get("name", "Unknown")
            alerts.append({
                "type": "error",
                "icon": "❌",
                "message": f"Cron failed: {name}",
                "severity": "high",
            })
    
    # Context alerts
    for session in sessions:
        pct = session.get("contextPct", 0.0)
        if pct > context_threshold:
            name = session.get("name", "Unknown")
            if len(name) > 30:
                name = name[:30]
            alerts.append({
                "type": "warning",
                "icon": "⚠️",
                "message": f"High context: {name} ({pct:.0f}%)",
                "severity": "medium",
            })
    
    # Gateway offline alerts
    if gateway_status.get("status") == "offline":
        alerts.append({
            "type": "error",
            "icon": "🔴",
            "message": "Gateway is offline",
            "severity": "critical",
        })
    
    # Memory alerts
    rss = gateway_status.get("rss", 0)
    if isinstance(rss, int) and rss > memory_threshold_kb:
        memory = gateway_status.get("memory", f"{rss} KB")
        alerts.append({
            "type": "warning",
            "icon": "🧠",
            "message": f"High memory usage: {memory}",
            "severity": "medium",
        })
    
    return alerts


def BuildCostBreakdown(model_costs: dict[str, dict]) -> list[dict]:
    """Rank models by cost.
    
    Ported from Go's apprefresh.BuildCostBreakdown().
    
    Args:
        model_costs: Dict mapping model names to TokenBucket-like dicts
        
    Returns:
        Sorted list of {model, cost} dicts, descending by cost
    """
    pairs = []
    for model, bucket in model_costs.items():
        if isinstance(bucket, dict):
            cost = bucket.get("cost", 0.0)
            if cost > 0:
                pairs.append((model, cost))
    
    # Sort by cost descending
    pairs.sort(key=lambda x: x[1], reverse=True)
    
    return [
        {"model": m, "cost": round2(c)}
        for m, c in pairs
    ]


def FmtTokens(count: int) -> str:
    """Format token counts for display.
    
    Ported from Go's apprefresh.FmtTokens().
    
    Examples:
        1234567 -> "1.2M"
        500 -> "500"
        1500 -> "1.5K"
        1000000 -> "1.0M"
        1000000000 -> "1.0B"
    """
    if count >= 1_000_000_000:
        return f"{count / 1_000_000_000:.1f}B"
    if count >= 1_000_000:
        return f"{count / 1_000_000:.1f}M"
    if count >= 1_000:
        return f"{count / 1_000:.1f}K"
    return str(count)
