"""Runtime AI credits — customer-facing unit for livechat, voice, and future runtime models.

1 displayed credit ≈ US$0.001 of published provider list price.
The ledger stores millicredits (1 credit = 1_000 millicredits) so settlement stays integer.
Customers never see raw tokens or provider dollars.
"""
from __future__ import annotations

import math
from dataclasses import dataclass
from typing import Optional

DISPLAY_SCALE = 1_000  # millicredits per displayed credit
TOKENS_PER_MILLION = 1_000_000

# millicredits per 1M tokens (or per minute for audio/STT meters).
# Derived from public list prices at 1 credit = $0.001.
_TOKEN_RATES: dict[tuple[str, str, str], int] = {
    # OpenAI chat / realtime text
    ("openai", "gpt-4.1-mini", "input_tokens"): 400,
    ("openai", "gpt-4.1-mini", "output_tokens"): 1_600,
    ("openai", "gpt-4.1", "input_tokens"): 2_000,
    ("openai", "gpt-4.1", "output_tokens"): 8_000,
    ("openai", "gpt-4o", "input_tokens"): 2_500,
    ("openai", "gpt-4o", "output_tokens"): 10_000,
    ("openai", "gpt-4o-mini", "input_tokens"): 150,
    ("openai", "gpt-4o-mini", "output_tokens"): 600,
    ("openai", "gpt-4o-realtime-preview", "input_tokens"): 5_000,
    ("openai", "gpt-4o-realtime-preview", "output_tokens"): 20_000,
    ("openai", "gpt-4o-realtime-preview", "audio_input_seconds"): 1_000,  # ~$0.06/min
    ("openai", "gpt-4o-realtime-preview", "audio_output_seconds"): 2_000,
    # Anthropic
    ("anthropic", "claude-haiku-4", "input_tokens"): 800,
    ("anthropic", "claude-haiku-4", "output_tokens"): 4_000,
    ("anthropic", "claude-sonnet-4", "input_tokens"): 3_000,
    ("anthropic", "claude-sonnet-4", "output_tokens"): 15_000,
    ("anthropic", "claude-opus-4", "input_tokens"): 15_000,
    ("anthropic", "claude-opus-4", "output_tokens"): 75_000,
    # Google
    ("google_gemini", "gemini-2.0-flash", "input_tokens"): 100,
    ("google_gemini", "gemini-2.0-flash", "output_tokens"): 400,
    ("google_gemini", "gemini-2.5-pro", "input_tokens"): 1_250,
    ("google_gemini", "gemini-2.5-pro", "output_tokens"): 10_000,
    # Deepgram STT — millicredits per second
    ("deepgram", "nova-3", "stt_seconds"): 72,
}

_ALIASES = {
    "gpt-4.1-mini": ("openai", "gpt-4.1-mini"),
    "gpt-4o-realtime-preview": ("openai", "gpt-4o-realtime-preview"),
    "gpt-4o-realtime-preview-2024-12-17": ("openai", "gpt-4o-realtime-preview"),
    "claude-haiku-4": ("anthropic", "claude-haiku-4"),
    "claude-sonnet-4": ("anthropic", "claude-sonnet-4"),
    "gemini-2.0-flash": ("google_gemini", "gemini-2.0-flash"),
    "nova-3": ("deepgram", "nova-3"),
}

_DEFAULT_CHAT = ("openai", "gpt-4.1-mini")
_DEFAULT_REALTIME = ("openai", "gpt-4o-realtime-preview")


@dataclass(frozen=True)
class MeterCharge:
    provider: str
    model: str
    meter: str
    quantity: int
    millicredits: int


def millicredits_to_credits(millicredits: int) -> float:
    return millicredits / DISPLAY_SCALE


def credits_to_millicredits(credits: int | float) -> int:
    return int(math.ceil(float(credits) * DISPLAY_SCALE))


def _normalize(provider: Optional[str], model: Optional[str]) -> tuple[str, str]:
    raw_model = (model or "").strip()
    if raw_model in _ALIASES:
        return _ALIASES[raw_model]
    prov = (provider or "openai").strip() or "openai"
    if prov in ("google", "gemini"):
        prov = "google_gemini"
    if not raw_model:
        return _DEFAULT_CHAT
    return (prov, raw_model)


def millicredits_for_meter(provider: str | None, model: str | None, meter: str, quantity: int) -> int:
    if quantity <= 0:
        return 0
    prov, mdl = _normalize(provider, model)
    rate = _TOKEN_RATES.get((prov, mdl, meter))
    if rate is None:
        fallback_model = _DEFAULT_REALTIME[1] if "audio" in meter or "realtime" in mdl else _DEFAULT_CHAT[1]
        fallback_prov = _DEFAULT_REALTIME[0] if fallback_model == _DEFAULT_REALTIME[1] else _DEFAULT_CHAT[0]
        rate = _TOKEN_RATES.get((fallback_prov, fallback_model, meter), 400)
    if meter.endswith("_seconds"):
        return max(1, quantity * rate) if quantity else 0
    return max(1, math.ceil(quantity * rate / TOKENS_PER_MILLION)) if quantity else 0


def millicredits_for_usage(
    *,
    provider: str | None,
    model: str | None,
    input_tokens: int = 0,
    output_tokens: int = 0,
    audio_input_seconds: int = 0,
    audio_output_seconds: int = 0,
    stt_seconds: int = 0,
) -> int:
    total = 0
    total += millicredits_for_meter(provider, model, "input_tokens", input_tokens)
    total += millicredits_for_meter(provider, model, "output_tokens", output_tokens)
    total += millicredits_for_meter(provider, model, "audio_input_seconds", audio_input_seconds)
    total += millicredits_for_meter(provider, model, "audio_output_seconds", audio_output_seconds)
    total += millicredits_for_meter(provider, model, "stt_seconds", stt_seconds)
    return total


def estimate_livechat_millicredits(provider: str | None, model: str | None, max_tokens: int) -> int:
    input_tokens = 800
    output_tokens = max(1, min(max_tokens, 4096))
    return millicredits_for_usage(
        provider=provider, model=model, input_tokens=input_tokens, output_tokens=output_tokens
    )


def estimate_voice_millicredits(model: str | None, estimated_minutes: int) -> int:
    seconds = max(1, estimated_minutes) * 60
    return millicredits_for_usage(
        provider="openai",
        model=model or "gpt-4o-realtime-preview",
        audio_input_seconds=seconds,
        audio_output_seconds=seconds,
    )


def list_price_rows() -> list[dict[str, str | int]]:
    rows = []
    for (provider, model, meter), rate in sorted(_TOKEN_RATES.items()):
        unit = "second" if meter.endswith("_seconds") else "1m_tokens"
        rows.append({
            "provider": provider,
            "model": model,
            "meter": meter,
            "millicredits_per_unit": rate,
            "unit": unit,
        })
    return rows
