"""
Tests for core.calculations — reproduces the Forrester Amazon Connect TEI
totals from the published study within rounding.

The PDF reports (Cash Flow Analysis, p.25):
    Benefits PV (RA)  = $101,696,791
    Costs PV    (RA)  = $ 22,983,076
    NPV               = $ 78,713,715
    ROI               = 342%
    Payback           = <6 months
"""

from __future__ import annotations

import pytest

from core.calculations import (
    SCENARIOS,
    apply_scenario,
    discount_factor,
    npv,
    payback_months,
    payback_years,
    present_value,
    present_value_series,
    risk_adjust_benefit,
    risk_adjust_cost,
    roi,
    roi_percentage,
)

# ─────────────────────────────────────────────
# Building blocks
# ─────────────────────────────────────────────


class TestDiscounting:
    def test_discount_factor_year_zero(self):
        assert discount_factor(0, 0.10) == pytest.approx(1.0)

    def test_discount_factor_known_value(self):
        # 1/(1.10)^3
        assert discount_factor(3, 0.10) == pytest.approx(0.7513148, rel=1e-5)

    def test_negative_year_raises(self):
        with pytest.raises(ValueError):
            discount_factor(-1, 0.10)

    def test_present_value_year_one(self):
        assert present_value(110, 1, 0.10) == pytest.approx(100.0)

    def test_present_value_series_three_years(self):
        # 100 each year for 3 years at 10% → ≈ 248.685
        assert present_value_series([100, 100, 100], 0.10) == pytest.approx(
            248.685, rel=1e-3
        )


class TestNPV:
    def test_zero_initial(self):
        assert npv([100, 100], 0.0) == pytest.approx(200.0)

    def test_with_initial(self):
        # 1000 invested up-front, 600 returned each of 2 years at 10%
        result = npv([600, 600], 0.10, initial=-1000)
        # PV of returns ≈ 545.45 + 495.87 = 1041.32, NPV ≈ 41.32
        assert result == pytest.approx(41.32, abs=0.5)


class TestRiskAdjustment:
    def test_benefit_zero_risk(self):
        assert risk_adjust_benefit(100, 0.0) == 100

    def test_benefit_15pct(self):
        assert risk_adjust_benefit(100, 0.15) == pytest.approx(85.0)

    def test_cost_5pct_upward(self):
        assert risk_adjust_cost(100, 0.05) == pytest.approx(105.0)

    def test_clamping(self):
        assert risk_adjust_benefit(100, 1.5) == 0  # clamped to 1.0
        assert risk_adjust_benefit(100, -0.5) == 100  # clamped to 0


class TestROI:
    def test_zero_costs_returns_zero(self):
        assert roi(100, 0) == 0.0

    def test_known(self):
        assert roi_percentage(101_696_791, 22_983_076) == pytest.approx(342, abs=1)


class TestPayback:
    def test_immediate(self):
        assert payback_years(0, [100]) == 0.0

    def test_amazon_connect_under_six_months(self):
        # Initial $1.196M, Y1 net ~$20M → quick crossing
        years = payback_years(1_196_250, [19_997_953, 31_562_489, 47_443_905])
        assert years is not None
        assert payback_months(1_196_250, [19_997_953, 31_562_489, 47_443_905]) < 6

    def test_never_recovered(self):
        assert payback_years(1000, [100, 100, 100]) is None


class TestScenarios:
    def test_default_scenarios_present(self):
        assert set(SCENARIOS) == {"conservative", "moderate", "aggressive"}

    def test_moderate_is_passthrough(self):
        items = [
            {
                "table": "benefits",
                "field_key": "x",
                "year_values": {"1": 1000, "2": 2000},
                "risk_adjustment": 0.15,
            }
        ]
        out = apply_scenario(items, "moderate")
        assert out[0]["year_values"] == {"1": 1000.0, "2": 2000.0}
        assert out[0]["risk_adjustment"] == pytest.approx(0.15)

    def test_conservative_lowers_benefits(self):
        items = [
            {
                "table": "benefits",
                "field_key": "x",
                "year_values": {"1": 1000},
                "risk_adjustment": 0.15,
            }
        ]
        out = apply_scenario(items, "conservative")
        assert out[0]["year_values"]["1"] == pytest.approx(800.0)
        # 0.15 + 0.10 = 0.25
        assert out[0]["risk_adjustment"] == pytest.approx(0.25)

    def test_aggressive_increases_benefits(self):
        items = [
            {
                "table": "benefits",
                "field_key": "x",
                "year_values": {"1": 1000},
                "risk_adjustment": 0.15,
            }
        ]
        out = apply_scenario(items, "aggressive")
        assert out[0]["year_values"]["1"] == pytest.approx(1150.0)
        assert out[0]["risk_adjustment"] == pytest.approx(0.10)

    def test_unknown_scenario_raises(self):
        with pytest.raises(KeyError):
            apply_scenario([], "purple")


# ─────────────────────────────────────────────
# End-to-end: reproduce the PDF totals
# ─────────────────────────────────────────────


class TestAmazonConnectComposite:
    """Reproduce the Forrester Amazon Connect TEI numbers within rounding."""

    DISCOUNT_RATE = 0.10
    EXPECTED_BENEFITS_PV = 101_696_791
    EXPECTED_COSTS_PV = 22_983_076
    EXPECTED_NPV = 78_713_715
    EXPECTED_ROI = 342  # percent
    TOLERANCE = 1_500  # dollars; PDF rounding at thousands

    def _benefits_pv(self, seed) -> float:
        total = 0.0
        for b in seed.BENEFITS:
            rf = b["risk_adjustment"]
            yr = [b["year_values"][str(y)] for y in (1, 2, 3)]
            yr_ra = [risk_adjust_benefit(v, rf) for v in yr]
            total += npv(yr_ra, self.DISCOUNT_RATE)
        return total

    def _costs_pv(self, seed) -> float:
        total = 0.0
        for c in seed.COSTS:
            rf = c["risk_adjustment"]
            init = risk_adjust_cost(c.get("initial") or 0, rf)
            yr = [c["year_values"][str(y)] for y in (1, 2, 3)]
            yr_ra = [risk_adjust_cost(v, rf) for v in yr]
            total += npv(yr_ra, self.DISCOUNT_RATE, initial=init)
        return total

    def test_benefits_pv(self, amazon_connect_seed):
        result = self._benefits_pv(amazon_connect_seed)
        assert result == pytest.approx(self.EXPECTED_BENEFITS_PV, abs=self.TOLERANCE)

    def test_costs_pv(self, amazon_connect_seed):
        result = self._costs_pv(amazon_connect_seed)
        assert result == pytest.approx(self.EXPECTED_COSTS_PV, abs=self.TOLERANCE)

    def test_npv(self, amazon_connect_seed):
        b = self._benefits_pv(amazon_connect_seed)
        c = self._costs_pv(amazon_connect_seed)
        assert (b - c) == pytest.approx(self.EXPECTED_NPV, abs=self.TOLERANCE)

    def test_roi(self, amazon_connect_seed):
        b = self._benefits_pv(amazon_connect_seed)
        c = self._costs_pv(amazon_connect_seed)
        assert roi_percentage(b, c) == pytest.approx(self.EXPECTED_ROI, abs=1)
