# -*- coding: utf-8 -*-
"""Single source of truth for selectable AI models, defaults and prices.

Verified against the providers' official docs on 2026-10-08. resources/settings.xml `values=` lists
must match MODELS; UsageTracker.get_pricing reads PRICES (via price_for).

Prices are USD per 1M tokens (standard tier).
"""

import datetime

MODELS = {
    'openai': ['gpt-6-luna', 'gpt-6.1-sol', 'gpt-6-astra'],
    'anthropic': ['claude-haiku-5-5', 'claude-sonnet-5-5', 'claude-opus-5-5'],
    'google': ['gemini-3.8-flash', 'gemini-3.5-flash-lite', 'gemini-3.1-pro-preview'],
    'mistral': ['mistral-medium-latest', 'mistral-large-4', 'mistral-small-latest',
                'ministral-14b-latest', 'ministral-8b-latest', 'ministral-3b-latest'],
    'groq': ['openai/gpt-oss-120b', 'openai/gpt-oss-20b', 'qwen/qwen3.8-27b'],
}

DEFAULT_MODEL = {provider: models[0] for provider, models in MODELS.items()}

SETTING_ID = {
    'openai': 'openai_model',
    'anthropic': 'anthropic_model',
    'google': 'google_model',
    'mistral': 'mistral_model',
    'groq': 'groq_model',
}

# Per model: input, cached_input (cache read; a model without it bills cached tokens like input), output, and optionally
#   cache_write  price of tokens written to a prompt cache (Anthropic 5-minute write; else billed like input),
#   long_context {above, input, cached_input, output}  the whole request is billed at these prices when its prompt is
#                longer than `above` tokens,
#   from         {YYYY-MM-DD: {price overrides}}  announced price changes, applied from that day on (see price_for).
PRICES = {
    'openai': {
        'gpt-6-astra': {'input': 10.00, 'cached_input': 1.00, 'output': 50.00,
                        'long_context': {'above': 272_000, 'input': 20.00, 'cached_input': 2.00, 'output': 75.00}},
        'gpt-6.1-sol': {'input': 2.00, 'cached_input': 0.10, 'output': 10.00,
                        'long_context': {'above': 272_000, 'input': 4.00, 'cached_input': 0.20, 'output': 15.00}},
        'gpt-6-luna': {'input': 0.10, 'cached_input': 0.01, 'output': 0.50,
                       'long_context': {'above': 272_000, 'input': 0.20, 'cached_input': 0.02, 'output': 0.75}},
    },
    'anthropic': {
        # Haiku 5.5 bills a prompt over 100K tokens at higher prices (the whole request); cache reads cost 0.1x input
        'claude-haiku-5-5': {'input': 0.10, 'cached_input': 0.01, 'cache_write': 0.125, 'output': 0.50,
                             'long_context': {'above': 100_000, 'input': 0.50, 'cached_input': 0.05, 'cache_write': 0.625,
                                              'output': 2.50}},
        'claude-sonnet-5-5': {'input': 2.00, 'cached_input': 0.10, 'cache_write': 2.50, 'output': 10.00},
        'claude-opus-5-5': {'input': 4.00, 'cached_input': 0.20, 'cache_write': 5.00, 'output': 20.00},
    },
    'google': {
        'gemini-3.8-flash': {'input': 0.75, 'cached_input': 0.075, 'output': 3.75,
                             'from': {'2027-01-01': {'input': 1.50, 'cached_input': 0.15, 'output': 7.50}}},
        'gemini-3.5-flash-lite': {'input': 0.30, 'cached_input': 0.03, 'output': 2.50},
        'gemini-3.1-pro-preview': {'input': 2.00, 'cached_input': 0.20, 'output': 12.00,
                                   'long_context': {'above': 200_000, 'input': 4.00, 'cached_input': 0.40, 'output': 18.00}},
    },
    'mistral': {
        'mistral-medium-latest': {'input': 1.50, 'output': 7.50},
        # public preview launch price; regular price is $1.36 / $0.14 / $4.18
        'mistral-large-4': {'input': 0.68, 'cached_input': 0.07, 'output': 2.09},
        'mistral-small-latest': {'input': 0.15, 'output': 0.60},
        'ministral-14b-latest': {'input': 0.20, 'output': 0.20},
        'ministral-8b-latest': {'input': 0.15, 'output': 0.15},
        'ministral-3b-latest': {'input': 0.10, 'output': 0.10},
    },
    'groq': {
        # prompt caching (automatic, 50% off cached input) exists for the gpt-oss models only
        'openai/gpt-oss-120b': {'input': 0.15, 'cached_input': 0.075, 'output': 0.60},
        'openai/gpt-oss-20b': {'input': 0.075, 'cached_input': 0.0375, 'output': 0.30},
        'qwen/qwen3.8-27b': {'input': 0.80, 'output': 4.00},
    },
}

# Claude models that accept output_config.effort (they all think adaptively)
CLAUDE_EFFORT_MODELS = ('claude-haiku-5-5', 'claude-sonnet-5-5', 'claude-opus-5-5')
# ... and the ones whose refusals the API retries server-side (`fallbacks`); Haiku 5.5 has no server-side fallback
CLAUDE_FALLBACK_MODELS = ('claude-sonnet-5-5', 'claude-opus-5-5')

TTS_MODELS = {
    'openai': ['gpt-4o-mini-tts', 'gpt-4o-mini-tts-2025-12-15', 'gpt-4o-mini-tts-2025-03-20'],
    'elevenlabs': ['eleven_flash_v2_5', 'eleven_v4_turbo', 'eleven_v4', 'eleven_v3'],
}

# ElevenLabs models that interpret inline [audio tags] instead of reading them aloud
ELEVENLABS_AUDIO_TAG_MODELS = ('eleven_v3', 'eleven_v4', 'eleven_v4_turbo')


def price_for(provider, model, today=None):
    """Prices of a catalog model valid on `today` (price changes from PRICES[..]['from'] applied), or None if unknown."""
    base = PRICES.get(provider, {}).get(model)
    if base is None:
        return None
    day = (today or datetime.date.today()).isoformat()
    price = {key: value for key, value in base.items() if key != 'from'}
    for start, change in sorted(base.get('from', {}).items()):
        if start <= day:
            price.update(change)
    return price


def model_or_default(provider, model):
    """Return model if it is currently selectable, else the provider default."""
    if model in MODELS.get(provider, ()):
        return model
    return DEFAULT_MODEL.get(provider, model)


def migrate_model_settings(addon):
    """Replace stored models that are no longer offered with the provider default.

    Returns a list of (setting_id, old, new) changes.
    """
    changes = []
    for provider, setting_id in SETTING_ID.items():
        stored = addon.getSetting(setting_id)
        if stored and stored not in MODELS[provider]:
            new = DEFAULT_MODEL[provider]
            addon.setSetting(setting_id, new)
            changes.append((setting_id, stored, new))
    tts_stored = addon.getSetting('milionar_tts_model')
    if tts_stored and tts_stored not in TTS_MODELS['openai']:
        addon.setSetting('milionar_tts_model', TTS_MODELS['openai'][0])
        changes.append(('milionar_tts_model', tts_stored, TTS_MODELS['openai'][0]))
    return changes
