"""Analytical and accounting checks; Python standard library only."""

import csv
import math
import unittest
from uuid import uuid4
from datetime import date
from pathlib import Path

import study


class StudyTests(unittest.TestCase):
    def setUp(self):
        self.test_directory = Path(__file__).resolve().parent / (".test-" + uuid4().hex)
        self.test_directory.mkdir()

    def tearDown(self):
        # Only files created inside this test's own directory are removed.
        for path in self.test_directory.iterdir():
            path.unlink()
        self.test_directory.rmdir()

    def make_csv(self, rows):
        path = self.test_directory / (uuid4().hex + ".csv")
        with path.open("w", newline="", encoding="utf-8") as stream:
            writer = csv.writer(stream)
            writer.writerow(study.INPUT_COLUMNS)
            writer.writerows(rows)
        return path

    def test_known_sample_covariance_and_perfect_correlation(self):
        covariance = study.sample_covariance([[1, 2, 3], [2, 4, 6]])
        self.assertEqual(covariance, [[1, 2], [2, 4]])
        self.assertEqual(study.correlation_from_covariance(covariance), [[1, 1], [1, 1]])

    def test_constant_series_has_undefined_correlation(self):
        correlation = study.correlation_from_covariance(study.sample_covariance([[0, 0, 0], [1, 2, 3]]))
        self.assertIsNone(correlation[0][0])
        self.assertIsNone(correlation[0][1])
        self.assertIsNone(correlation[1][0])

    def test_equal_volatility_closed_form_and_perfect_correlation(self):
        for rho in [0, 0.5, 0.9, 1]:
            risk = study.portfolio_risk([1 / 3] * 3, study.covariance_from_volatility([0.01] * 3, rho))
            expected = 0.01 * math.sqrt((1 + 2 * rho) / 3)
            self.assertAlmostEqual(risk["portfolio_daily_volatility"], expected, places=14)
            self.assertAlmostEqual(risk["diversification_ratio"], 0.01 / expected, places=14)

    def test_euler_components_sum_and_scale_with_risky_allocation(self):
        covariance = study.covariance_from_volatility([0.01, 0.02, 0.03], 0.3)
        full = study.portfolio_risk([0.4, 0.35, 0.25], covariance)
        cash = study.portfolio_risk([0.32, 0.28, 0.2], covariance)
        self.assertAlmostEqual(sum(full["component_daily_volatility"]), full["portfolio_daily_volatility"], places=14)
        self.assertAlmostEqual(sum(full["fraction_of_total_volatility"]), 1, places=14)
        self.assertAlmostEqual(cash["portfolio_daily_volatility"], 0.8 * full["portfolio_daily_volatility"], places=14)
        for actual, expected in zip(cash["fraction_of_total_volatility"], full["fraction_of_total_volatility"]):
            self.assertAlmostEqual(actual, expected, places=14)
        two_bot = study.portfolio_risk([0.5, 0.5], study.covariance_from_volatility([0.02, 0.01], 0))
        self.assertAlmostEqual(two_bot["portfolio_daily_volatility"], math.sqrt(0.000125), places=14)
        self.assertAlmostEqual(two_bot["fraction_of_total_volatility"][0], 0.8, places=14)
        self.assertAlmostEqual(two_bot["fraction_of_total_volatility"][1], 0.2, places=14)

    def test_assumed_stress_dollars_and_cash_scaling(self):
        full = study.joint_stress(100000, [0.4, 0.35, 0.25], [-0.08, -0.06, -0.1])
        cash = study.joint_stress(100000, [0.32, 0.28, 0.2], [-0.08, -0.06, -0.1])
        self.assertAlmostEqual(full["portfolio_pnl"], -7800)
        self.assertAlmostEqual(sum(full["sleeve_pnl"]), full["portfolio_pnl"])
        self.assertAlmostEqual(cash["portfolio_pnl"], -6240)
        self.assertAlmostEqual(cash["cash_weight"], 0.2)

    def test_no_rebalancing_accounting_and_nonadditive_drawdown(self):
        observations = [(date(2026, 1, 1), (0.1, -0.1, 0)), (date(2026, 1, 2), (-0.1, 0.1, 0))]
        rows, summary = study.equity_path(observations, 100000, [0.5, 0.5, 0])
        self.assertAlmostEqual(rows[0]["portfolio_equity"], 100000)
        self.assertAlmostEqual(rows[1]["bot_a_opening_weight"], 0.55)
        self.assertAlmostEqual(rows[1]["portfolio_equity"], 99000)
        self.assertAlmostEqual(summary["daily_close_max_drawdown"], 0.01)
        self.assertAlmostEqual(summary["initial_weighted_average_of_sleeve_max_drawdowns"], 0.1)
        self.assertAlmostEqual(sum(summary["sleeve_final_equity"]) + summary["cash_equity"], summary["final_equity"])

    def test_known_zero_valid_but_blank_and_nonfinite_rejected(self):
        valid = self.make_csv([["2026-01-01", 0, 0.01, 0.02], ["2026-01-02", 0.01, 0, -0.02]])
        self.assertEqual(study.read_returns(valid)[0][1][0], 0)
        for invalid in ["", "NaN", "inf", "-inf", "-1.01"]:
            path = self.make_csv([["2026-01-01", 0, 0.01, 0.02], ["2026-01-02", invalid, 0, -0.02]])
            with self.assertRaises(ValueError):
                study.read_returns(path)

    def test_missing_duplicate_and_reversed_dates_rejected(self):
        for second_date in ["2026-01-03", "2026-01-01", "2025-12-31"]:
            path = self.make_csv([["2026-01-01", 0, 0.01, 0.02], [second_date, 0.01, 0, -0.02]])
            with self.assertRaises(ValueError):
                study.read_returns(path)

    def test_incomplete_alignment_and_impossible_weights_rejected(self):
        with self.assertRaises(ValueError):
            study.sample_covariance([[0, 1], [0]])
        for weights in [[0.6, 0.6, 0], [-0.1, 0.5, 0.6], [float("nan"), 0.5, 0.5]]:
            with self.assertRaises(ValueError):
                study.check_weights(weights)
        with self.assertRaises(ValueError):
            study.covariance_from_volatility([0.01] * 3, -0.6)

    def test_fixture_complete_and_all_results_accounting_reconcile(self):
        source = Path(__file__).resolve().parent / "sleeve-returns.csv"
        result = study.run(source, self.test_directory)
        self.assertEqual(result["observations"], 24)
        self.assertEqual(result["start"], "2026-09-01")
        self.assertEqual(result["end"], "2026-09-24")
        equity = result["constructed_fixture_equity"]
        self.assertAlmostEqual(equity["net_pnl"], equity["final_equity"] - 100000)
        self.assertAlmostEqual(sum(equity["final_weights"]), 1)
        self.assertEqual(len(list(self.test_directory.glob("*.csv"))), 4)


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