"""
Unit tests for server/acct_engines.py.

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

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


class TbDiagnosticsTests(unittest.TestCase):
    HEAD_NATURE = {"trade_pay": "credit", "cash": "debit"}

    def test_suspense_name_detected(self):
        out = ae.tb_diagnostics([{"name": "Suspense account", "group": "", "dr": 5000, "cr": 0, "head": ""}])
        self.assertEqual(len(out["suspense_candidates"]), 1)

    def test_unusual_balance_wrong_side(self):
        out = ae.tb_diagnostics([{"name": "Trade payables", "group": "", "dr": 10000, "cr": 0, "head": "trade_pay"}],
                                head_nature=self.HEAD_NATURE, materiality=100)
        self.assertEqual(len(out["unusual_balances"]), 1)
        self.assertEqual(out["unusual_balances"][0]["expected_side"], "credit")

    def test_correct_side_not_flagged(self):
        out = ae.tb_diagnostics([{"name": "Trade payables", "group": "", "dr": 0, "cr": 10000, "head": "trade_pay"}],
                                head_nature=self.HEAD_NATURE, materiality=100)
        self.assertEqual(len(out["unusual_balances"]), 0)

    def test_unclassified_material_balance(self):
        out = ae.tb_diagnostics([{"name": "Misc account", "group": "", "dr": 5000, "cr": 0, "head": ""}], materiality=100)
        self.assertEqual(len(out["unclassified"]), 1)

    def test_immaterial_unclassified_not_flagged(self):
        out = ae.tb_diagnostics([{"name": "Misc account", "group": "", "dr": 50, "cr": 0, "head": ""}], materiality=100)
        self.assertEqual(len(out["unclassified"]), 0)

    def test_large_movement_flagged(self):
        out = ae.tb_diagnostics([{"name": "Cash", "group": "", "dr": 200000, "cr": 0, "head": "cash", "prev": 100000}],
                                head_nature=self.HEAD_NATURE, materiality=100, movement_threshold_pct=20)
        self.assertEqual(len(out["large_movements"]), 1)
        self.assertEqual(out["large_movements"][0]["variance_pct"], 100.0)

    def test_small_movement_not_flagged(self):
        out = ae.tb_diagnostics([{"name": "Cash", "group": "", "dr": 105000, "cr": 0, "head": "cash", "prev": 100000}],
                                head_nature=self.HEAD_NATURE, materiality=100, movement_threshold_pct=20)
        self.assertEqual(len(out["large_movements"]), 0)

    def test_balanced_tb(self):
        out = ae.tb_diagnostics([{"name": "A", "group": "", "dr": 1000, "cr": 0}, {"name": "B", "group": "", "dr": 0, "cr": 1000}])
        self.assertTrue(out["balanced"])
        self.assertEqual(out["tie_out_diff"], 0.0)

    def test_empty_no_crash(self):
        out = ae.tb_diagnostics([])
        self.assertTrue(out["balanced"])
        self.assertEqual(out["total_dr"], 0.0)


class SuggestPoliciesTests(unittest.TestCase):
    def test_always_includes_baseline(self):
        out = ae.suggest_policies([])
        keys = [p["key"] for p in out]
        self.assertIn("basis", keys)
        self.assertIn("estimates", keys)
        self.assertNotIn("ppe", keys)

    def test_ppe_present_includes_ppe_policy(self):
        out = ae.suggest_policies(["ppe"])
        self.assertIn("ppe", [p["key"] for p in out])

    def test_tax_heads_map_to_one_policy(self):
        out = ae.suggest_policies(["current_tax", "deferred_tax", "dtl"])
        keys = [p["key"] for p in out]
        self.assertEqual(keys.count("tax"), 1)


class CashFlowStatementTests(unittest.TestCase):
    def test_ties_out(self):
        out = ae.cash_flow_statement([
            {"section": "operating_pat_start", "label": "PAT", "amount": 500000},
            {"section": "investing", "label": "Capex", "amount": -200000},
            {"section": "financing", "label": "Loan repaid", "amount": -100000},
        ], opening_cash=50000, closing_cash_actual=250000)
        self.assertTrue(out["ties_out"])
        self.assertEqual(out["reconciliation_diff"], 0.0)

    def test_does_not_tie_out(self):
        out = ae.cash_flow_statement([{"section": "operating_pat_start", "label": "PAT", "amount": 500000}],
                                     opening_cash=0, closing_cash_actual=100000)
        self.assertFalse(out["ties_out"])

    def test_unknown_section_defaults_to_operating_adjustment(self):
        out = ae.cash_flow_statement([{"section": "bogus", "label": "X", "amount": 1000}])
        self.assertEqual(len(out["by_section"]["operating_adjustment"]), 1)

    def test_no_actual_closing_no_tie_out_key(self):
        out = ae.cash_flow_statement([{"section": "operating_pat_start", "label": "PAT", "amount": 1000}])
        self.assertNotIn("ties_out", out)


class EquityMovementTests(unittest.TestCase):
    def test_closing_balance_formula(self):
        out = ae.equity_movement([{"name": "Retained earnings", "opening_balance": 300000, "profit_for_year": 500000,
                                   "dividend": 100000, "oci": -2000}])
        self.assertEqual(out["components"][0]["closing_balance"], 698000.0)

    def test_totals_sum_across_components(self):
        out = ae.equity_movement([{"name": "A", "opening_balance": 100}, {"name": "B", "opening_balance": 200}])
        self.assertEqual(out["totals"]["opening_balance"], 300.0)

    def test_empty_no_crash(self):
        out = ae.equity_movement([])
        self.assertEqual(out["components"], [])


class ProvisionMovementTests(unittest.TestCase):
    def test_closing_formula(self):
        out = ae.provision_movement([{"item": "Bonus", "opening_balance": 50000, "charge_for_year": 80000, "utilized": 50000}])
        self.assertEqual(out["rows"][0]["closing_balance"], 80000.0)

    def test_reversed_reduces_closing(self):
        out = ae.provision_movement([{"item": "Warranty", "opening_balance": 10000, "reversed": 4000}])
        self.assertEqual(out["rows"][0]["closing_balance"], 6000.0)


class PrepaymentAmortizationTests(unittest.TestCase):
    def test_mid_year_elapsed_months(self):
        out = ae.prepayment_amortization(
            [{"item": "Insurance", "total_amount": 12000, "start_date": "2081-04-01", "end_date": "2082-03-31"}],
            as_of="2081-10-15")
        row = out["rows"][0]
        self.assertEqual(row["total_months"], 12)
        self.assertEqual(row["elapsed_months"], 7)
        self.assertEqual(row["amortized_to_date"], 7000.0)
        self.assertEqual(row["balance_carried_forward"], 5000.0)

    def test_as_of_after_end_date_caps_at_total(self):
        out = ae.prepayment_amortization(
            [{"item": "Insurance", "total_amount": 12000, "start_date": "2081-04-01", "end_date": "2081-09-30"}],
            as_of="2082-01-01")
        row = out["rows"][0]
        self.assertEqual(row["amortized_to_date"], 12000.0)
        self.assertEqual(row["balance_carried_forward"], 0.0)

    def test_missing_dates_no_crash(self):
        out = ae.prepayment_amortization([{"item": "X", "total_amount": 1000}], as_of="2081-01-01")
        self.assertEqual(out["rows"][0]["total_months"], 0)


class FxRevaluationTests(unittest.TestCase):
    def test_liability_revalued_higher_is_a_loss(self):
        out = ae.fx_revaluation([{"item": "Payable", "foreign_amount": 1000, "booked_rate": 132, "closing_rate": 135, "classification": "liability"}])
        self.assertEqual(out["rows"][0]["gain_loss"], -3000.0)

    def test_asset_revalued_higher_is_a_gain(self):
        out = ae.fx_revaluation([{"item": "Receivable", "foreign_amount": 1000, "booked_rate": 130, "closing_rate": 135, "classification": "asset"}])
        self.assertEqual(out["rows"][0]["gain_loss"], 5000.0)


class InterestAccrualTests(unittest.TestCase):
    def test_simple_full_year(self):
        out = ae.interest_accrual([{"item": "FD", "principal": 100000, "rate_pct": 10, "from_date": "2081-01-01",
                                    "to_date": "2082-01-01", "day_count_basis": 365, "compounding": "simple"}])
        self.assertEqual(out["rows"][0]["accrued_interest"], 10000.0)

    def test_annual_compounding_exceeds_simple_over_multiple_years(self):
        simple = ae.interest_accrual([{"item": "X", "principal": 100000, "rate_pct": 10, "from_date": "2079-01-01",
                                       "to_date": "2081-01-01", "day_count_basis": 365, "compounding": "simple"}])
        annual = ae.interest_accrual([{"item": "X", "principal": 100000, "rate_pct": 10, "from_date": "2079-01-01",
                                       "to_date": "2081-01-01", "day_count_basis": 365, "compounding": "annual"}])
        self.assertGreater(annual["rows"][0]["accrued_interest"], simple["rows"][0]["accrued_interest"])


class BorrowingCostCapitalizationTests(unittest.TestCase):
    def test_weighted_average_rate(self):
        out = ae.borrowing_cost_capitalization([], [{"principal": 1000000, "rate_pct": 10}, {"principal": 500000, "rate_pct": 8}],
                                                800000, period_days=365)
        self.assertAlmostEqual(out["weighted_avg_capitalization_rate_pct"], 9.33, places=2)

    def test_general_capitalized_capped_at_actual_cost(self):
        out = ae.borrowing_cost_capitalization([], [{"principal": 100000, "rate_pct": 10}], 5000000, period_days=365)
        self.assertEqual(out["general_capitalized"], out["general_borrowing_cost_incurred"])

    def test_specific_cost_net_of_temp_investment_income(self):
        out = ae.borrowing_cost_capitalization([{"principal": 500000, "rate_pct": 12}], [], 0,
                                                period_days=365, income_from_temp_investment=5000)
        self.assertEqual(out["specific_cost"], 55000.0)

    def test_no_general_borrowings_no_crash(self):
        out = ae.borrowing_cost_capitalization([{"principal": 100000, "rate_pct": 10}], [], 500000)
        self.assertEqual(out["general_capitalized"], 0.0)


class ChecklistSummaryTests(unittest.TestCase):
    def test_completion_pct(self):
        out = ae.checklist_summary([{"area": "Closing", "status": "done"}, {"area": "Closing", "status": "pending"}])
        self.assertEqual(out["Closing"]["completion_pct"], 50.0)

    def test_empty_no_crash(self):
        self.assertEqual(ae.checklist_summary([]), {})


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