"""
Unit tests for server/tp_engines.py — the Transfer Pricing pure-function
engines (benchmarking statistics, working-capital adjustment, method
recommendation, risk scoring, adjustment exposure).

Run with:  cd server && ../venv/bin/python3 -m unittest tests.test_tp_engines -v
"""
import os
import sys
import unittest

sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import tp_engines as tp  # noqa: E402


class BenchmarkStatsTests(unittest.TestCase):
    def test_empty_set(self):
        out = tp.compute_benchmark_stats([])
        self.assertEqual(out["count"], 0)
        self.assertIsNone(out["mean"])

    def test_single_value(self):
        out = tp.compute_benchmark_stats([{"value": 7.5}])
        self.assertEqual(out["count"], 1)
        self.assertEqual(out["min"], 7.5)
        self.assertEqual(out["max"], 7.5)
        self.assertEqual(out["median"], 7.5)
        self.assertIsNone(out["adjusted_range"])  # fewer than 5 points

    def test_known_distribution(self):
        values = [{"value": v} for v in (3.9, 4.2, 5.0, 5.8, 6.5, 7.1)]
        out = tp.compute_benchmark_stats(values)
        self.assertEqual(out["count"], 6)
        self.assertEqual(out["min"], 3.9)
        self.assertEqual(out["max"], 7.1)
        self.assertAlmostEqual(out["mean"], sum(v["value"] for v in values) / 6, places=2)
        self.assertIsNotNone(out["adjusted_range"])
        self.assertGreaterEqual(out["adjusted_range"]["min"], out["min"])
        self.assertLessEqual(out["adjusted_range"]["max"], out["max"])
        self.assertLessEqual(out["q1"], out["median"])
        self.assertLessEqual(out["median"], out["q3"])

    def test_weighted_average_uses_weights(self):
        # Two comparables, one weighted far more heavily — the weighted
        # average should sit much closer to the heavily-weighted value than
        # the plain mean does.
        values = [{"value": 2.0, "weight": 1}, {"value": 10.0, "weight": 9}]
        out = tp.compute_benchmark_stats(values)
        plain_mean = (2.0 + 10.0) / 2
        self.assertGreater(out["weighted_average"], plain_mean)
        self.assertAlmostEqual(out["weighted_average"], 9.2, places=1)

    def test_ignores_null_values(self):
        out = tp.compute_benchmark_stats([{"value": 5.0}, {"value": None}, {"value": 6.0}])
        self.assertEqual(out["count"], 2)


class WorkingCapitalAdjustmentTests(unittest.TestCase):
    def test_missing_sales_returns_none(self):
        out = tp.working_capital_adjustment({"sales": 0}, {"sales": 1000}, 10)
        self.assertIsNone(out["adjustment_pct"])
        self.assertIn("missing", out["note"].lower())

    def test_higher_tested_party_nwc_gives_positive_adjustment(self):
        # Tested party carries more working capital relative to sales than
        # the comparable — adjustment should be positive at a positive rate.
        comparable = {"receivables": 100000, "inventory": 50000, "payables": 30000, "sales": 1000000}
        tested = {"receivables": 200000, "inventory": 80000, "payables": 40000, "sales": 1000000}
        out = tp.working_capital_adjustment(comparable, tested, 10)
        self.assertGreater(out["adjustment_pct"], 0)
        self.assertAlmostEqual(out["tested_party_nwc_ratio"], 24.0, places=1)
        self.assertAlmostEqual(out["comparable_nwc_ratio"], 12.0, places=1)

    def test_identical_ratios_give_zero_adjustment(self):
        same = {"receivables": 100000, "inventory": 50000, "payables": 30000, "sales": 1000000}
        out = tp.working_capital_adjustment(same, dict(same), 10)
        self.assertEqual(out["adjustment_pct"], 0)


class MethodRecommendationTests(unittest.TestCase):
    def test_internal_cup_wins_regardless_of_other_facts(self):
        out = tp.recommend_method({"transaction_category": "royalty"}, {"functional_profile": "ip_owner"},
                                   {"internal_cup": True, "unique_intangible": True})
        self.assertEqual(out["recommended_method"], "cup")

    def test_financing_prefers_cup(self):
        out = tp.recommend_method({"transaction_category": "financing"}, {}, {})
        self.assertEqual(out["recommended_method"], "cup")

    def test_limited_risk_distributor_with_gross_margin_data_prefers_rpm(self):
        out = tp.recommend_method({"transaction_category": "goods"},
                                   {"functional_profile": "limited_risk_distributor"},
                                   {"external_gross_margin": True})
        self.assertEqual(out["recommended_method"], "rpm")

    def test_limited_risk_distributor_without_gross_margin_data_falls_back_to_tnmm(self):
        out = tp.recommend_method({"transaction_category": "goods"},
                                   {"functional_profile": "limited_risk_distributor"}, {})
        self.assertEqual(out["recommended_method"], "tnmm")

    def test_contract_manufacturer_with_markup_data_prefers_cost_plus(self):
        out = tp.recommend_method({"transaction_category": "goods"},
                                   {"functional_profile": "contract_manufacturer"},
                                   {"external_gross_margin": True})
        self.assertEqual(out["recommended_method"], "cpm")

    def test_unique_intangible_prefers_profit_split(self):
        out = tp.recommend_method({"transaction_category": "royalty"}, {"functional_profile": "entrepreneurial"},
                                   {"unique_intangible": True})
        self.assertEqual(out["recommended_method"], "psm")

    def test_no_clear_signal_defaults_to_tnmm(self):
        out = tp.recommend_method({"transaction_category": "services"}, {"functional_profile": "service_provider"}, {})
        self.assertEqual(out["recommended_method"], "tnmm")

    def test_every_recommendation_carries_a_basis(self):
        for cat, profile, ca in [
            ("goods", "limited_risk_distributor", {"external_gross_margin": True}),
            ("financing", "", {}),
            ("services", "service_provider", {}),
        ]:
            out = tp.recommend_method({"transaction_category": cat}, {"functional_profile": profile}, ca)
            self.assertTrue(out["basis"], "every recommendation must explain itself")


class RiskScoringTests(unittest.TestCase):
    def test_clean_small_transaction_is_low_risk(self):
        out = tp.score_risk({"amount": 50000}, {"functional_profile": "limited_risk_distributor"},
                             {"stats": {"count": 5}, "in_range": True}, {})
        self.assertEqual(out["risk_level"], "Low")
        self.assertEqual(out["score"], 0)

    def test_multiple_red_flags_escalate_to_very_high(self):
        out = tp.score_risk({"amount": 15000000}, {"functional_profile": "limited_risk_distributor"},
                             {"stats": {"count": 1}, "in_range": False},
                             {"loss_making": True, "persistent_losses": True, "cross_border": True,
                              "sensitive_category": True, "related_party_pct_high": True})
        self.assertEqual(out["risk_level"], "Very High")
        self.assertGreaterEqual(out["score"], 8)

    def test_missing_far_counts_as_documentation_gap(self):
        out = tp.score_risk({"amount": 0}, {}, {"stats": {"count": 5}, "in_range": True}, {})
        factor_names = [f["factor"] for f in out["factors"]]
        self.assertIn("Documentation gap", factor_names)

    def test_score_is_monotonic_in_risk_level(self):
        levels_seen = []
        for score_flags in ({}, {"loss_making": True}, {"loss_making": True, "cross_border": True, "sensitive_category": True}):
            out = tp.score_risk({"amount": 0}, {"functional_profile": "x"},
                                 {"stats": {"count": 5}, "in_range": True}, score_flags)
            levels_seen.append(_RISK_ORDER.index(out["risk_level"]))
        self.assertEqual(levels_seen, sorted(levels_seen))


_RISK_ORDER = ["Low", "Medium", "High", "Very High"]


class AdjustmentExposureTests(unittest.TestCase):
    def test_missing_actual_or_benchmark_returns_none_not_zero(self):
        out = tp.compute_adjustment_exposure({"amount": 1000}, {"stats": {}})
        self.assertIsNone(out["adjustment_amount"])
        self.assertIsNotNone(out["note"])

    def test_computes_adjustment_when_below_arm_length(self):
        out = tp.compute_adjustment_exposure({"actual_result": 2.5, "amount": 5000000},
                                              {"stats": {"median": 5.0}}, tax_rate_pct=25)
        self.assertEqual(out["adjustment_amount"], 125000.0)
        self.assertEqual(out["tax_impact"], 31250.0)
        self.assertIsNone(out["interest_exposure"])  # no rate supplied
        self.assertIsNotNone(out["note"])  # explains what wasn't computed and why

    def test_no_rates_supplied_leaves_everything_but_the_core_adjustment_none(self):
        out = tp.compute_adjustment_exposure({"actual_result": 2.5, "amount": 5000000}, {"stats": {"median": 5.0}})
        self.assertIsNotNone(out["adjustment_amount"])
        self.assertIsNone(out["tax_impact"])
        self.assertIsNone(out["interest_exposure"])
        self.assertIsNone(out["penalty_exposure"])
        self.assertIsNone(out["total_exposure"])

    def test_above_arm_length_gives_negative_adjustment(self):
        out = tp.compute_adjustment_exposure({"actual_result": 8.0, "amount": 1000000}, {"stats": {"median": 5.0}})
        self.assertLess(out["adjustment_amount"], 0)

    def test_arm_length_override_wins_over_benchmark_median(self):
        out = tp.compute_adjustment_exposure({"actual_result": 2.5, "amount": 1000000}, {"stats": {"median": 5.0}},
                                              arm_length_result=6.0)
        self.assertEqual(out["arm_length_result"], 6.0)


class ScenarioAnalysisTests(unittest.TestCase):
    def _bench(self):
        return {"stats": {"q1": 4.1, "median": 4.8, "q3": 5.5}}

    def test_five_scenarios_when_user_defined_supplied(self):
        out = tp.compute_scenarios({"actual_result": 2.5, "amount": 5000000}, self._bench(),
                                    tax_rate_pct=25, user_defined_arm_length=6.0)
        names = [s["scenario"] for s in out["scenarios"]]
        self.assertEqual(names, ["Base Case", "Low Adjustment", "Median Adjustment", "High Adjustment", "User-Defined"])

    def test_four_scenarios_without_user_defined(self):
        out = tp.compute_scenarios({"actual_result": 2.5, "amount": 5000000}, self._bench(), tax_rate_pct=25)
        self.assertEqual(len(out["scenarios"]), 4)

    def test_base_case_has_zero_adjustment(self):
        out = tp.compute_scenarios({"actual_result": 2.5, "amount": 5000000}, self._bench(), tax_rate_pct=25)
        base = out["scenarios"][0]
        self.assertEqual(base["adjustment_amount"], 0)
        self.assertEqual(base["tax_impact"], 0)

    def test_exposure_is_monotonic_low_median_high(self):
        out = tp.compute_scenarios({"actual_result": 2.5, "amount": 5000000}, self._bench(), tax_rate_pct=25)
        low, median, high = out["scenarios"][1], out["scenarios"][2], out["scenarios"][3]
        self.assertLess(low["adjustment_amount"], median["adjustment_amount"])
        self.assertLess(median["adjustment_amount"], high["adjustment_amount"])

    def test_each_scenario_carries_a_description(self):
        out = tp.compute_scenarios({"actual_result": 2.5, "amount": 5000000}, self._bench(), tax_rate_pct=25)
        self.assertTrue(all(s["description"] for s in out["scenarios"]))


if __name__ == "__main__":
    unittest.main()
