"""Hand-derived accounting checks for the educational example, not market validation."""
import copy
from fractions import Fraction as F
import json
from pathlib import Path
import unittest

from accounting import run_case, csv_text


def day(date="2026-01-05", price="98", **kwargs):
    return dict(date=date, open=price, close=price) | kwargs


def case(shares="1", cash="0", days=None, fee="0"):
    return dict(name="test", initial_shares=shares, initial_cash=cash,
                previous_close="100", fee_rate=fee, days=days or [])


def ex_day(**kwargs):
    return day(**(dict(dividend="2", cash_available="2026-01-07") | kwargs))


class AccountingTests(unittest.TestCase):
    def test_receivable_counts_as_equity_but_not_cash(self):
        row = run_case(case(days=[ex_day()]))[0]
        self.assertEqual((row["shares"], row["cash"], row["receivable"], row["equity"]),
                         (F(1), F(0), F(2), F(100)))
        self.assertEqual(row["index_level"], F(100))

    def test_index_and_non_reinvesting_holder_diverge(self):
        rows = run_case(case(days=[ex_day(), day("2026-01-06", "107.80")]))
        self.assertEqual(rows[-1]["equity"], F("109.80"))
        self.assertEqual(rows[-1]["index_level"], F(110))

    def test_sale_preserves_claim_and_payment_does_not_create_income_twice(self):
        rows = run_case(case(days=[ex_day(action="sell_all", close="108"),
                                  day("2026-01-07", "108"), day("2026-01-08", "108")]))
        self.assertEqual((rows[0]["shares"], rows[0]["cash"], rows[0]["receivable"]), (0, 98, 2))
        self.assertEqual([r["equity"] for r in rows], [100, 100, 100])
        self.assertEqual([r["cash"] for r in rows], [98, 100, 100])
        self.assertEqual(rows[-1]["receivable"], 0)

    def test_ex_date_buyer_has_no_entitlement(self):
        row = run_case(case("0", "98", [ex_day(action="buy_cash")]))[0]
        self.assertEqual((row["shares"], row["receivable"], row["equity"]), (1, 0, 98))

    def test_receivable_cannot_fund_purchase_but_delivered_cash_can(self):
        rows = run_case(case(days=[ex_day(action="sell_all"),
            day("2026-01-06", "100", action="buy_notional", notional="100"),
            day("2026-01-07", "100", action="buy_notional", notional="100")]))
        self.assertEqual(rows[1]["order_status"], "rejected_insufficient_cash")
        self.assertEqual((rows[1]["shares"], rows[1]["cash"], rows[1]["receivable"]), (0, 98, 2))
        self.assertEqual((rows[2]["shares"], rows[2]["cash"], rows[2]["receivable"]), (1, 0, 0))

    def test_fee_is_funded_inside_available_cash(self):
        row = run_case(case("0", "100", [day(price="100", action="buy_cash")], "0.01"))[0]
        self.assertEqual(row["share_value"], F(10000, 101))
        self.assertEqual(row["fees"], F(100, 101))
        self.assertEqual(row["cash"], 0)
        self.assertEqual(row["equity"] + row["fees"], 100)

    def test_unfunded_fee_rejects_whole_order(self):
        row = run_case(case("0", "100", [day(price="100", action="buy_notional", notional="100")], "0.01"))[0]
        self.assertEqual(row["order_status"], "rejected_insufficient_cash")
        self.assertEqual((row["shares"], row["cash"], row["fees"]), (0, 100, 0))

    def test_split_after_entitlement_does_not_double_receivable(self):
        rows = run_case(case(days=[ex_day(), day("2026-01-06", "49", split="2")]))
        self.assertEqual((rows[-1]["shares"], rows[-1]["receivable"], rows[-1]["equity"]), (2, 2, 100))
        self.assertEqual(rows[-1]["index_level"], 100)

    def test_same_session_split_uses_dividend_per_post_split_share(self):
        row = run_case(case(days=[day(price="49", split="2", dividend="1", cash_available="2026-01-07")]))[0]
        self.assertEqual((row["shares"], row["receivable"], row["index_level"]), (2, 2, 100))

    def test_invalid_inputs_are_rejected(self):
        variants = [case(days=[day(price="0")]), case(shares="-1", days=[day()]),
                    case(days=[day(action="buuy")]), case(days=[ex_day(cash_available="2026-01-04")]),
                    case(days=[day("2026-01-06"), day("2026-01-05")]),
                    case(days=[day(dividend="2")]), case(days=[day(typo="2")]),
                    case(days=[day(action="buy_notional", notional="-1")])]
        for value in variants:
            with self.subTest(value=value), self.assertRaises(ValueError):
                run_case(value)

    def test_inputs_are_not_mutated_and_output_matches_supplied_results(self):
        here = Path(__file__).resolve().parent
        inputs = json.loads((here / "scenarios.json").read_text(encoding="utf-8"))
        original = copy.deepcopy(inputs)
        rows = [row for scenario in inputs for row in run_case(scenario)]
        self.assertEqual(inputs, original)
        self.assertEqual(csv_text(rows), (here / "expected.csv").read_text(encoding="utf-8"))
        for row in rows:
            self.assertEqual(row["equity"], row["cash"] + row["share_value"] + row["receivable"])
            self.assertGreaterEqual(row["cash"], 0)


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