"""Sweeps and tornado data."""

from __future__ import annotations

import pytest
from genesyscalc.meters import TokenPrice
from genesyscalc.model import CalculatorInputs, calculate
from genesyscalc.sensitivity import (
    DRIVERS,
    driver_value,
    set_driver,
    sweep,
    tornado,
)
from genesyscalc.tts import TtsUsage
from genesyscalc.usage import DeflectionMix, VoiceBotUsage, Volumes


@pytest.fixture
def base() -> CalculatorInputs:
    return CalculatorInputs(
        volumes=Volumes(voice_inbound_monthly=100_000, voice_outbound_monthly=20_000),
        users=500,
        enabled=frozenset(
            {"agent_copilot_named", "bots_voice", "virtual_agent"}
        ),
        mix=DeflectionMix(bot_only_share=0.30, virtual_agent_share=0.15),
        price=TokenPrice(),
        voice_bot=VoiceBotUsage(calls_per_month=30_000, avg_bot_seconds_per_call=40),
        tts=TtsUsage(calls_monthly=100_000),
    )


def test_driver_round_trip(base: CalculatorInputs) -> None:
    assert driver_value(base, "users") == 500
    assert driver_value(set_driver(base, "users", 750), "users") == 750


def test_nested_driver_round_trip(base: CalculatorInputs) -> None:
    assert driver_value(base, "concession_pct") == 0.0
    moved = set_driver(base, "concession_pct", 0.25)
    assert moved.price.concession_pct == pytest.approx(0.25)
    assert base.price.concession_pct == 0.0  # inputs are frozen; no mutation


def test_integer_drivers_stay_integers(base: CalculatorInputs) -> None:
    assert isinstance(set_driver(base, "users", 512.6).users, int)
    assert set_driver(base, "users", 512.6).users == 513


def test_unknown_driver_is_named(base: CalculatorInputs) -> None:
    with pytest.raises(KeyError, match="not a known driver"):
        driver_value(base, "vibes")


def test_sweep_is_monotone_in_token_price(base: CalculatorInputs) -> None:
    rows = sweep(base, "token_price", [0.5, 1.0, 1.5, 2.0])
    costs = [r["token_cost_annual"] for r in rows]
    assert costs == sorted(costs)
    assert len(rows) == 4


def test_sweep_carries_the_driver_value(base: CalculatorInputs) -> None:
    rows = sweep(base, "concession_pct", [0.0, 0.25])
    assert rows[0]["concession_pct"] == 0.0
    assert rows[1]["grand_total_annual"] < rows[0]["grand_total_annual"]


def test_tornado_is_sorted_by_swing(base: CalculatorInputs) -> None:
    rows = tornado(base, ["token_price", "users", "bot_seconds", "concession_pct"])
    swings = [r["swing"] for r in rows]
    assert swings == sorted(swings, reverse=True)
    assert all(r["swing"] >= 0 for r in rows)


def test_tornado_brackets_the_baseline(base: CalculatorInputs) -> None:
    baseline = calculate(base).grand_total_annual
    for row in tornado(base, ["token_price", "users"]):
        assert row["low"] <= baseline <= row["high"]
        assert row["baseline"] == pytest.approx(baseline)


def test_tornado_skips_inactive_levers() -> None:
    """No TTS and no voice bot in the scenario — those levers are noise."""
    plain = CalculatorInputs(
        volumes=Volumes(voice_inbound_monthly=1_000),
        users=10,
        enabled=frozenset({"agent_copilot_named"}),
    )
    drivers = [r["driver"] for r in tornado(plain, list(DRIVERS))]
    assert "bot_seconds" not in drivers
    assert "tts_chars_per_call" not in drivers
    assert "users" in drivers


def test_tornado_skips_a_lever_that_would_break_the_partition() -> None:
    """Raising a share past the partition is illegal, not a data point."""
    saturated = CalculatorInputs(
        volumes=Volumes(voice_inbound_monthly=1_000),
        users=10,
        enabled=frozenset({"virtual_agent"}),
        mix=DeflectionMix(bot_only_share=0.5, virtual_agent_share=0.5),
    )
    drivers = [r["driver"] for r in tornado(saturated, ["virtual_agent_share"])]
    assert drivers == []


def test_invalid_delta_rejected(base: CalculatorInputs) -> None:
    with pytest.raises(ValueError, match="delta"):
        tornado(base, ["users"], delta=1.5)
