"""
BIBIVIDI — Billing tests (TODO-13)
Tests for bvd/billing.py: model price table, compute_bill(), monthly_billing().
"""
from __future__ import annotations

from datetime import UTC

import pytest

from bvd.billing import (
    DEFAULT_PRICE_EUR,
    MODEL_PRICES_EUR,
    TIERS,
    BillingResult,
    compute_bill,
    model_price_eur,
    monthly_billing,
)

# ── model_price_eur ────────────────────────────────────────────────────────────

def test_model_price_exact_match():
    price = model_price_eur("claude-haiku-4-5-20251001")
    assert price == MODEL_PRICES_EUR["claude-haiku-4-5-20251001"]
    assert price > 0


def test_model_price_prefix_match():
    # "claude-sonnet-4-6-20260801" → falls back to "claude-sonnet-4-6" prefix
    price = model_price_eur("claude-sonnet-4-6-20260801")
    assert price == MODEL_PRICES_EUR["claude-sonnet-4-6"]


def test_model_price_unknown_model_returns_default():
    price = model_price_eur("completely-unknown-model-xyz")
    assert price == DEFAULT_PRICE_EUR


def test_model_price_empty_string_returns_default():
    assert model_price_eur("") == DEFAULT_PRICE_EUR


def test_model_price_case_insensitive():
    upper = model_price_eur("GPT-4O")
    lower = model_price_eur("gpt-4o")
    assert upper == lower


def test_all_listed_models_have_positive_price():
    for name, price in MODEL_PRICES_EUR.items():
        assert price > 0, f"Price for {name!r} must be positive"


# ── compute_bill ───────────────────────────────────────────────────────────────

def test_compute_bill_free_tier():
    result = compute_bill(
        1000.0, "free", executions=100, tokens_saved=1_000_000, year=2026, month=3
    )
    assert result.bibividi_share_eur == 0.0
    assert result.tier == "free"
    assert result.tier_rate == 0.0
    assert not result.capped


def test_compute_bill_pro_tier_below_cap():
    # €50 savings × 20% = €10, cap is €200 → not capped
    result = compute_bill(50.0, "pro", executions=100, tokens_saved=500_000, year=2026, month=3)
    assert abs(result.bibividi_share_eur - 10.0) < 0.0001
    assert not result.capped


def test_compute_bill_pro_tier_above_cap():
    # €2000 savings × 20% = €400, cap is €200 → capped
    result = compute_bill(2000.0, "pro", executions=100, tokens_saved=2_000_000, year=2026, month=3)
    assert result.bibividi_share_eur == 200.0
    assert result.capped


def test_compute_bill_growth_tier_cap():
    # €4000 × 15% = €600, cap is €500 → capped
    result = compute_bill(
        4000.0, "growth", executions=100, tokens_saved=4_000_000, year=2026, month=3
    )
    assert result.bibividi_share_eur == 500.0
    assert result.capped


def test_compute_bill_enterprise_no_cap():
    # €50000 × 10% = €5000, no cap
    result = compute_bill(
        50000.0, "enterprise", executions=1000, tokens_saved=50_000_000, year=2026, month=3
    )
    assert abs(result.bibividi_share_eur - 5000.0) < 0.01
    assert not result.capped


def test_compute_bill_unknown_tier_defaults_to_pro():
    result = compute_bill(
        100.0, "unknown-tier", executions=10, tokens_saved=100_000, year=2026, month=3
    )
    # TIERS.get("unknown-tier", TIERS["pro"]) → pro rates
    assert result.tier_rate == TIERS["pro"][0]


def test_compute_bill_result_fields():
    result = compute_bill(100.0, "pro", executions=42, tokens_saved=1_000_000, year=2026, month=3)
    assert isinstance(result, BillingResult)
    assert result.year == 2026
    assert result.month == 3
    assert result.executions == 42
    assert result.tokens_saved == 1_000_000
    assert result.savings_eur == 100.0


# ── monthly_billing ────────────────────────────────────────────────────────────

@pytest.mark.asyncio
async def test_monthly_billing_empty_db(tmp_path):
    """Returns zero billing result when no executions are recorded."""
    from unittest.mock import patch

    db = tmp_path / "test.db"
    with patch("bvd.config.settings.db_path", db):
        from bvd.engine.library import Library
        lib = Library()
        await lib.init()
        await lib.close()

        result = await monthly_billing(2026, 3, "pro")

    assert result.executions == 0
    assert result.tokens_saved == 0
    assert result.savings_eur == 0.0
    assert result.bibividi_share_eur == 0.0
    assert result.year == 2026
    assert result.month == 3


@pytest.mark.asyncio
async def test_monthly_billing_with_executions(tmp_path):
    """Billing correctly sums token savings × model price from the executions table."""
    from datetime import datetime
    from unittest.mock import patch

    import aiosqlite

    db = tmp_path / "test.db"
    price = model_price_eur("claude-haiku-4-5-20251001")

    with patch("bvd.config.settings.db_path", db):
        from bvd.engine.library import Library
        lib = Library()
        await lib.init()
        await lib.close()

        # Insert a fake script and a fake execution
        async with aiosqlite.connect(db) as conn:
            await conn.execute(
                "INSERT INTO scripts (id, name, description, code, created_at, updated_at) "
                "VALUES (?, ?, ?, ?, ?, ?)",
                ("test-skill", "Test", "Test skill", "print('x')",
                 datetime.now(UTC).isoformat(),
                 datetime.now(UTC).isoformat()),
            )
            # 10,000 tokens saved at known price
            await conn.execute(
                "INSERT INTO executions "
                "(script_id, tokens_before, tokens_after, latency_ms, executed_at, "
                "model_name, model_price_per_token_eur) "
                "VALUES (?, ?, ?, ?, ?, ?, ?)",
                ("test-skill", 10000, 0, 100.0,
                 datetime(2026, 3, 15, tzinfo=UTC).isoformat(),
                 "claude-haiku-4-5-20251001", price),
            )
            await conn.commit()

        result = await monthly_billing(2026, 3, "pro")

    assert result.executions == 1
    assert result.tokens_saved == 10000
    expected_savings = round(10000 * price, 4)  # compute_bill rounds to 4dp
    assert result.savings_eur == expected_savings
    # Pro rate 20%, not capped at €200
    assert abs(result.bibividi_share_eur - expected_savings * 0.20) < 1e-4


@pytest.mark.asyncio
async def test_monthly_billing_uses_default_price_for_null_rows(tmp_path):
    """Legacy rows without model_price_per_token_eur use DEFAULT_PRICE_EUR."""
    from datetime import datetime
    from unittest.mock import patch

    import aiosqlite

    db = tmp_path / "test.db"
    with patch("bvd.config.settings.db_path", db):
        from bvd.engine.library import Library
        lib = Library()
        await lib.init()
        await lib.close()

        async with aiosqlite.connect(db) as conn:
            await conn.execute(
                "INSERT INTO scripts (id, name, description, code, created_at, updated_at) "
                "VALUES (?, ?, ?, ?, ?, ?)",
                ("legacy", "Legacy", "desc", "print('x')",
                 datetime.now(UTC).isoformat(),
                 datetime.now(UTC).isoformat()),
            )
            await conn.execute(
                "INSERT INTO executions "
                "(script_id, tokens_before, tokens_after, latency_ms, executed_at) "
                "VALUES (?, ?, ?, ?, ?)",
                ("legacy", 5000, 0, 50.0,
                 datetime(2026, 3, 10, tzinfo=UTC).isoformat()),
            )
            await conn.commit()

        result = await monthly_billing(2026, 3, "pro")

    expected_savings = round(5000 * DEFAULT_PRICE_EUR, 4)
    assert result.savings_eur == expected_savings


@pytest.mark.asyncio
async def test_monthly_billing_filters_by_month(tmp_path):
    """Executions from other months are excluded."""
    from datetime import datetime
    from unittest.mock import patch

    import aiosqlite

    db = tmp_path / "test.db"
    price = DEFAULT_PRICE_EUR
    with patch("bvd.config.settings.db_path", db):
        from bvd.engine.library import Library
        lib = Library()
        await lib.init()
        await lib.close()

        async with aiosqlite.connect(db) as conn:
            await conn.execute(
                "INSERT INTO scripts (id, name, description, code, created_at, updated_at) "
                "VALUES (?, ?, ?, ?, ?, ?)",
                ("s1", "S1", "desc", "print('x')",
                 datetime.now(UTC).isoformat(),
                 datetime.now(UTC).isoformat()),
            )
            for month, tokens in [(3, 10000), (2, 50000), (4, 20000)]:
                await conn.execute(
                    "INSERT INTO executions "
                    "(script_id, tokens_before, tokens_after, latency_ms, executed_at, "
                    "model_price_per_token_eur) "
                    "VALUES (?, ?, ?, ?, ?, ?)",
                    ("s1", tokens, 0, 10.0,
                     datetime(2026, month, 15, tzinfo=UTC).isoformat(),
                     price),
                )
            await conn.commit()

        result = await monthly_billing(2026, 3, "free")

    assert result.tokens_saved == 10000   # only March


# ── schema migration (v4→v5) ──────────────────────────────────────────────────

@pytest.mark.asyncio
async def test_schema_v5_migration(tmp_path):
    """model_name and model_price_per_token_eur columns exist after migration."""
    from unittest.mock import patch

    import aiosqlite

    db = tmp_path / "test.db"
    with patch("bvd.config.settings.db_path", db):
        from bvd.engine.library import Library
        lib = Library()
        await lib.init()
        await lib.close()

    async with aiosqlite.connect(db) as conn:
        async with conn.execute("PRAGMA table_info(executions)") as cur:
            cols = {row[1] for row in await cur.fetchall()}

    assert "model_name" in cols
    assert "model_price_per_token_eur" in cols


# ── TODO-25/26: Free-tier model detection ─────────────────────────────────────

from bvd.billing import (
    FREE_TIER_MODELS,
    KNOWN_FREE_DAILY_TOKENS,
    free_tier_daily_tokens,
    is_free_tier_model,
)


def test_is_free_tier_model_gemini():
    assert is_free_tier_model("gemini-2.0-flash") is True
    assert is_free_tier_model("gemini-1.5-flash") is True


def test_is_free_tier_model_groq():
    assert is_free_tier_model("llama-3.3-70b-versatile") is True
    assert is_free_tier_model("llama3-8b-8192") is True


def test_is_free_tier_model_prefix_match():
    # "gemini-2.0-flash-001" prefix-matches "gemini-2.0-flash"
    assert is_free_tier_model("gemini-2.0-flash-001") is True


def test_is_free_tier_model_paid_returns_false():
    assert is_free_tier_model("claude-haiku-4-5-20251001") is False
    assert is_free_tier_model("gpt-4o") is False
    assert is_free_tier_model("unknown-model-xyz") is False


def test_model_price_free_tier_returns_zero():
    assert model_price_eur("gemini-2.0-flash") == 0.0
    assert model_price_eur("llama-3.3-70b-versatile") == 0.0
    assert model_price_eur("GEMINI-1.5-FLASH") == 0.0  # case insensitive


def test_free_tier_daily_tokens_known_model():
    limit = free_tier_daily_tokens("gemini-1.5-flash")
    assert limit == 1_000_000


def test_free_tier_daily_tokens_groq():
    limit = free_tier_daily_tokens("llama3-70b-8192")
    assert limit == 500_000


def test_free_tier_daily_tokens_prefix_match():
    limit = free_tier_daily_tokens("gemini-2.0-flash-exp-2026")
    assert limit == 1_000_000


def test_free_tier_daily_tokens_unknown_returns_none():
    assert free_tier_daily_tokens("unknown-model-xyz") is None
    assert free_tier_daily_tokens("claude-haiku-4-5") is None
