"""Tokens → dollars: the allowance, the rounding, and the commercial levers.

Hand-checked first, then pinned.
"""

from __future__ import annotations

import pytest
from genesyscalc.billing import (
    MONTHS_PER_YEAR,
    allowance_for,
    billable_tokens_monthly,
    cost_lines,
    total_cost,
)
from genesyscalc.meters import LicenceModel, TokenPrice

NAMED = LicenceModel.NAMED
CONCURRENT = LicenceModel.CONCURRENT


# ── the free monthly allowance ───────────────────────────────────────────


def test_free_allowance_deducted_named() -> None:
    """1,000 consumption tokens, named org: 1,000 − 250 = 750 billable."""
    assert billable_tokens_monthly(1_000, 0, NAMED) == 750


def test_free_allowance_deducted_concurrent() -> None:
    """Same consumption, concurrent org: 1,000 − 350 = 650."""
    assert billable_tokens_monthly(1_000, 0, CONCURRENT) == 650


def test_allowance_floors_at_zero() -> None:
    assert billable_tokens_monthly(120, 0, NAMED) == 0


def test_allowance_does_not_carry_over() -> None:
    """Two quiet months do not bank credit for a busy third.

    120 → 0 billable. 120 → 0 billable. 400 → 150 billable, NOT 0: the
    unused 130 + 130 is discarded, because the allowance "renew[s] each
    month and do[es] not carry over to future months".
    """
    assert billable_tokens_monthly(120, 0, NAMED) == 0
    assert billable_tokens_monthly(120, 0, NAMED) == 0
    assert billable_tokens_monthly(400, 0, NAMED) == 150


def test_allowance_can_be_switched_off() -> None:
    """So the gap is measurable on stage rather than merely asserted."""
    assert billable_tokens_monthly(1_000, 0, NAMED, apply_allowance=False) == 1_000
    assert allowance_for(NAMED, apply_allowance=False) == 0
    assert allowance_for(NAMED) == 250


def test_allowance_applies_across_consumption_and_subscription() -> None:
    """It is an org-level deduction, not a per-line one.

    44.12 consumption → ceil 45; + 20,000 subscription = 20,045; − 250 =
    19,795.
    """
    assert billable_tokens_monthly(44.117647, 20_000, NAMED) == 19_795


# ── rounding ─────────────────────────────────────────────────────────────


def test_consumption_tokens_rounded_up_monthly() -> None:
    """44.117647 tokens of bot time bills as 45."""
    assert billable_tokens_monthly(44.117647, 0, NAMED, apply_allowance=False) == 45


def test_per_user_tokens_are_exact_not_rounded() -> None:
    """500 named users × 40 = 20,000 exactly — no ceil on subscription."""
    assert billable_tokens_monthly(0, 20_000, NAMED, apply_allowance=False) == 20_000


def test_negative_tokens_rejected() -> None:
    with pytest.raises(ValueError, match="must not be negative"):
        billable_tokens_monthly(-1, 0, NAMED)


# ── price, concession, currency ──────────────────────────────────────────


def test_concession_applies_to_msrp() -> None:
    """$1.00 list, 30% concession → $0.70/token; 1,000 tokens → $700/mo."""
    price = TokenPrice(list_rate=1.00, concession_pct=0.30)
    assert price.effective_rate() == pytest.approx(0.70)
    totals = total_cost(1_000, 0, NAMED, price, apply_allowance=False)
    assert totals.token_cost_monthly == pytest.approx(700.0)
    assert totals.token_cost_annual == pytest.approx(8_400.0)


def test_msrp_walk_is_carried_alongside() -> None:
    """The deck-frame column: list beside effective, and the saving."""
    price = TokenPrice(list_rate=1.00, concession_pct=0.30)
    totals = total_cost(1_000, 0, NAMED, price, apply_allowance=False)
    assert totals.list_cost_annual == pytest.approx(12_000.0)
    assert totals.concession_saving_annual == pytest.approx(3_600.0)
    assert totals.token_cost_annual <= totals.list_cost_annual


def test_contracted_rate_overrides_concession() -> None:
    price = TokenPrice(list_rate=1.00, contracted_rate=0.55, concession_pct=0.30)
    assert price.effective_rate() == pytest.approx(0.55)


def test_currency_carried_through() -> None:
    price = TokenPrice(currency="JPY", list_rate=120.0)
    totals = total_cost(1_000, 0, NAMED, price, apply_allowance=False)
    assert totals.currency == "JPY"
    assert totals.token_cost_monthly == pytest.approx(120_000.0)


def test_invalid_concession_rejected() -> None:
    with pytest.raises(ValueError, match="concession_pct"):
        TokenPrice(concession_pct=1.0)


# ── cost lines ───────────────────────────────────────────────────────────


def test_cost_lines_price_each_meter_at_the_effective_rate() -> None:
    price = TokenPrice(list_rate=1.00, concession_pct=0.25)
    lines = cost_lines(
        {"ai_scoring": 100.0, "virtual_agent": 7_500.0},
        {"ai_scoring": 2_000.0, "virtual_agent": 15_000.0},
        price,
    )
    by_key = {line.key: line for line in lines}
    assert by_key["ai_scoring"].cost_monthly == pytest.approx(75.0)
    assert by_key["virtual_agent"].cost_monthly == pytest.approx(5_625.0)
    assert by_key["virtual_agent"].cost_annual == pytest.approx(
        5_625.0 * MONTHS_PER_YEAR
    )
    assert by_key["ai_scoring"].units_monthly == pytest.approx(2_000.0)
    assert by_key["ai_scoring"].unit_label == "evaluations"


def test_cost_lines_are_ordered_by_feature_name() -> None:
    lines = cost_lines(
        {"virtual_agent": 1.0, "ai_scoring": 1.0}, {}, TokenPrice()
    )
    assert [line.feature for line in lines] == ["AI Scoring", "Virtual Agent"]


def test_totals_expose_the_allowance_that_was_applied() -> None:
    totals = total_cost(1_000, 20_000, NAMED, TokenPrice())
    assert totals.allowance_tokens_monthly == 250
    assert totals.gross_tokens_monthly == pytest.approx(21_000.0)
    assert totals.billable_tokens_monthly == 20_750
