"""End-to-end pricing, hand-checked.

The reference scenario below is the one the notebook ships as its default.
Every figure was computed by hand from the published rates before it was
pinned here.
"""

from __future__ import annotations

import json

import pytest
from genesyscalc.appendix import result_json
from genesyscalc.meters import LicenceModel, TokenPrice
from genesyscalc.model import (
    CalculatorInputs,
    calculate,
    compare,
    cost_per_interaction,
    metered_units,
)
from genesyscalc.ratecard import RATE_CARD_SOURCE_DATE
from genesyscalc.tts import TtsUsage
from genesyscalc.usage import DeflectionMix, VoiceBotUsage, Volumes

REFERENCE_ENABLED = frozenset(
    {
        "agent_copilot_named",
        "speech_and_text_analytics_named",
        "bots_voice",
        "virtual_agent",
        "agentic_virtual_agent",
        "ai_summary_and_insights",
        "ai_scoring",
        "whatsapp_messaging",
        "predictive_engagement",
    }
)


@pytest.fixture
def volumes() -> Volumes:
    return Volumes(
        voice_inbound_monthly=100_000,
        voice_outbound_monthly=20_000,
        digital_sessions_monthly=8_000,
        email_monthly=12_000,
        messaging_monthly=6_000,
        evaluations_monthly=2_000,
    )


@pytest.fixture
def reference(volumes: Volumes) -> CalculatorInputs:
    return CalculatorInputs(
        volumes=volumes,
        users=500,
        licence=LicenceModel.NAMED,
        enabled=REFERENCE_ENABLED,
        mix=DeflectionMix(
            bot_only_share=0.30, virtual_agent_share=0.15, agentic_va_share=0.05
        ),
        price=TokenPrice(),
        voice_bot=VoiceBotUsage(
            calls_per_month=30_000, avg_bot_seconds_per_call=40
        ),
        tts=TtsUsage(calls_monthly=100_000, chars_per_call=2_000),
    )


# ── the hand-checked reference scenario ──────────────────────────────────


def test_subscription_lines(reference: CalculatorInputs) -> None:
    """500 users: Copilot 500×40 = 20,000; STA 500×30 = 15,000 tokens/mo."""
    by_key = {line.key: line for line in calculate(reference).lines}
    assert by_key["agent_copilot_named"].tokens_monthly == pytest.approx(20_000.0)
    assert by_key["speech_and_text_analytics_named"].tokens_monthly == pytest.approx(
        15_000.0
    )
    assert by_key["agent_copilot_named"].cost_annual == pytest.approx(240_000.0)


def test_voice_bot_line(reference: CalculatorInputs) -> None:
    """30,000 calls × ceil(40/15)×15s = 45s = 0.75 min → 22,500 min.
    22,500 / 17 = 1,323.53 tokens/mo → $15,882.35/yr at $1.00."""
    by_key = {line.key: line for line in calculate(reference).lines}
    assert by_key["bots_voice"].units_monthly == pytest.approx(22_500.0)
    assert by_key["bots_voice"].tokens_monthly == pytest.approx(1_323.529412, rel=1e-6)
    assert by_key["bots_voice"].cost_annual == pytest.approx(15_882.35, abs=0.01)


def test_virtual_agent_lines_use_the_partition(reference: CalculatorInputs) -> None:
    """120,000 voice: VA 15% = 18,000 × 0.5 = 9,000 tokens;
    AVA 5% = 6,000 × 1.2 = 7,200 tokens."""
    by_key = {line.key: line for line in calculate(reference).lines}
    assert by_key["virtual_agent"].units_monthly == pytest.approx(18_000.0)
    assert by_key["virtual_agent"].tokens_monthly == pytest.approx(9_000.0)
    assert by_key["agentic_virtual_agent"].units_monthly == pytest.approx(6_000.0)
    assert by_key["agentic_virtual_agent"].tokens_monthly == pytest.approx(7_200.0)


def test_summary_is_zeroed_because_copilot_is_on(reference: CalculatorInputs) -> None:
    result = calculate(reference)
    by_key = {line.key: line for line in result.lines}
    assert by_key["ai_summary_and_insights"].units_monthly == 0.0
    assert by_key["ai_summary_and_insights"].cost_annual == 0.0
    assert any("Agent Copilot is enabled" in w for w in result.warnings)


def test_zero_rated_line_is_shown_not_hidden(reference: CalculatorInputs) -> None:
    """A published $0 is a finding — it renders as a line, at zero."""
    by_key = {line.key: line for line in calculate(reference).lines}
    assert "predictive_engagement" in by_key
    assert by_key["predictive_engagement"].units_monthly > 0
    assert by_key["predictive_engagement"].cost_annual == 0.0


def test_reference_totals(reference: CalculatorInputs) -> None:
    """consumption 17,638.53 → ceil 17,639; + 35,000 subscription = 52,639;
    − 250 allowance = 52,389 billable → $52,389/mo → $628,668/yr.
    TTS 200M chars, 20% VA-free → 160M → $3,200/mo → $38,400/yr.
    Grand total $667,068/yr."""
    totals = calculate(reference).totals
    assert totals.consumption_tokens_monthly == pytest.approx(17_638.529412, rel=1e-6)
    assert totals.subscription_tokens_monthly == pytest.approx(35_000.0)
    assert totals.gross_tokens_monthly == pytest.approx(52_639.0)
    assert totals.allowance_tokens_monthly == 250
    assert totals.billable_tokens_monthly == 52_389
    assert totals.token_cost_annual == pytest.approx(628_668.0)


def test_reference_grand_total_splits_tokens_from_tts(
    reference: CalculatorInputs,
) -> None:
    result = calculate(reference)
    assert result.tts_cost_annual == pytest.approx(38_400.0)
    assert result.grand_total_annual == pytest.approx(667_068.0)
    assert result.grand_total_annual == pytest.approx(
        result.token_cost_annual + result.tts_cost_annual
    )


def test_cost_per_interaction(reference: CalculatorInputs) -> None:
    """$667,068 / (146,000 × 12) = $0.3807 per interaction."""
    result = calculate(reference)
    assert cost_per_interaction(result, reference.volumes) == pytest.approx(
        0.380747, rel=1e-5
    )


def test_roundup_warning_is_reported(reference: CalculatorInputs) -> None:
    assert any("15-second per-call round-up" in w for w in calculate(reference).warnings)


# ── structural ties that must hold at any setting ────────────────────────


def test_lines_sum_to_the_gross_token_count(reference: CalculatorInputs) -> None:
    result = calculate(reference)
    assert sum(line.tokens_monthly for line in result.lines) == pytest.approx(
        result.totals.consumption_tokens_monthly
        + result.totals.subscription_tokens_monthly
    )


def test_effective_cost_never_exceeds_list(reference: CalculatorInputs) -> None:
    discounted = reference.with_(price=TokenPrice(concession_pct=0.4))
    result = calculate(discounted)
    assert result.totals.token_cost_annual <= result.totals.list_cost_annual
    assert result.totals.concession_saving_annual >= 0


def test_allowance_only_ever_reduces_the_bill(reference: CalculatorInputs) -> None:
    with_allowance = calculate(reference).totals.billable_tokens_monthly
    without = calculate(
        reference.with_(apply_allowance=False)
    ).totals.billable_tokens_monthly
    assert with_allowance == without - 250


def test_allowance_absorbing_a_month_is_reported() -> None:
    quiet = CalculatorInputs(
        volumes=Volumes(voice_inbound_monthly=100),
        users=0,
        enabled=frozenset({"ai_scoring"}),
    )
    result = calculate(quiet)
    assert result.totals.billable_tokens_monthly == 0
    assert any("free monthly allowance" in w for w in result.warnings)


def test_unknown_feature_key_rejected(volumes: Volumes) -> None:
    with pytest.raises(ValueError, match="not published meters"):
        CalculatorInputs(volumes=volumes, users=1, enabled=frozenset({"telepathy"}))


def test_metered_units_covers_every_enabled_key(reference: CalculatorInputs) -> None:
    assert set(metered_units(reference)) == set(reference.enabled)


def test_empty_scenario_costs_nothing() -> None:
    result = calculate(CalculatorInputs(volumes=Volumes(), users=0))
    assert result.grand_total_annual == 0.0
    assert result.lines == ()


# ── licence model, scenarios ─────────────────────────────────────────────


def test_concurrent_licence_costs_more_per_user(reference: CalculatorInputs) -> None:
    """Copilot 40→60 and STA 30→45, so 35,000 → 52,500 subscription tokens."""
    concurrent = reference.with_(
        licence=LicenceModel.CONCURRENT,
        enabled=frozenset(
            {"agent_copilot_named", "speech_and_text_analytics_named"}
        ),
    )
    named = reference.with_(
        enabled=frozenset({"agent_copilot_named", "speech_and_text_analytics_named"})
    )
    assert calculate(named).totals.subscription_tokens_monthly == pytest.approx(35_000.0)
    assert calculate(concurrent).totals.subscription_tokens_monthly == pytest.approx(
        52_500.0
    )


def test_compare_is_monotone_in_deflection(reference: CalculatorInputs) -> None:
    """More virtual-agent deflection means more consumption tokens."""
    rows = compare(
        {
            "Conservative": reference.with_(
                mix=DeflectionMix(bot_only_share=0.20, virtual_agent_share=0.05)
            ),
            "Base": reference,
            "Aggressive": reference.with_(
                mix=DeflectionMix(
                    bot_only_share=0.35,
                    virtual_agent_share=0.25,
                    agentic_va_share=0.10,
                )
            ),
        }
    )
    assert [r["Scenario"] for r in rows] == ["Conservative", "Base", "Aggressive"]
    totals = [r["Token cost / yr"] for r in rows]
    assert totals[0] < totals[1] < totals[2]


# ── the export payload ───────────────────────────────────────────────────


def test_result_json_round_trips_and_carries_provenance(
    reference: CalculatorInputs,
) -> None:
    payload = result_json(calculate(reference), reference, meta={"client": "Example"})
    assert json.loads(json.dumps(payload)) == payload
    assert payload["rate_card"]["source_date"] == RATE_CARD_SOURCE_DATE
    assert payload["rate_card"]["voice_bot_roundup_seconds"] == 15
    assert payload["tts_source"]["source_date"] == "2026-05-22"
    assert payload["totals"]["grand_total_annual"] == pytest.approx(667_068.0)
    assert payload["meta"]["client"] == "Example"
    assert len(payload["lines"]) == len(REFERENCE_ENABLED)
