"""
Unit tests for server/country_service.py — the Country Engine's first slice.

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

sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))


class CountryServiceTests(unittest.TestCase):
    def setUp(self):
        self._tmpdir = tempfile.mkdtemp()
        os.environ["CA_DATA_DIR"] = self._tmpdir
        for mod in ("db", "country_service"):
            sys.modules.pop(mod, None)
        import db as db_mod
        import country_service as cs_mod
        global db, cs
        db, cs = db_mod, cs_mod
        db.init_db()

    def test_fresh_firm_defaults_to_nepal(self):
        self.assertEqual(cs.firm_country_code(1), "NP")

    def test_client_with_no_country_code_falls_back_to_firm(self):
        cid = db.execute("INSERT INTO client (firm_id, code, name) VALUES (1, 'CL-01', 'Test Client')")
        self.assertEqual(cs.client_country_code(cid, 1), "NP")

    def test_client_with_its_own_country_code_overrides_the_firm(self):
        db.execute("UPDATE firm SET country_code = 'NP' WHERE id = 1")
        cid = db.execute(
            "INSERT INTO client (firm_id, code, name, country_code) VALUES (1, 'CL-01', 'Indian Subsidiary', 'IN')")
        self.assertEqual(cs.client_country_code(cid, 1), "IN")

    def test_use_country_for_client_resolves_the_clients_own_jurisdiction(self):
        cid = db.execute(
            "INSERT INTO client (firm_id, code, name, country_code) VALUES (1, 'CL-01', 'Indian Subsidiary', 'IN')")
        with cs.use_country_for_client(1, cid):
            self.assertEqual(cs.current_country_code(), "IN")

    def test_use_country_for_client_with_no_client_id_falls_back_to_firm(self):
        db.execute("UPDATE firm SET country_code = 'AE' WHERE id = 1")
        with cs.use_country_for_client(1, None):
            self.assertEqual(cs.current_country_code(), "AE")

    def test_country_lookup_returns_registry_row(self):
        row = cs.country("NP")
        self.assertEqual(row["name"], "Nepal")
        self.assertEqual(row["currency"], "NPR")

    def test_unrecognised_country_code_returns_none(self):
        self.assertIsNone(cs.country("ZZZZ"))

    def test_unverified_count_with_no_argument_counts_every_country(self):
        # The right default for a context with no firm to scope by (run.py's
        # CLI output, the public /api/health check) — see db.unverified_count's
        # own docstring for why this differs from rates()/rate_dict()'s
        # ambient-context default.
        total = db.unverified_count()
        np_only = db.unverified_count("NP")
        in_only = db.unverified_count("IN")
        ae_only = db.unverified_count("AE")
        self.assertEqual(total, np_only + in_only + ae_only)
        self.assertGreater(np_only, 0)

    def test_unverified_count_scoped_to_a_country_excludes_others(self):
        np_count = db.unverified_count("NP")
        rows = db.query("SELECT COUNT(*) n FROM rate_master WHERE country_code = 'NP'"
                        " AND verification_status = 'UNVERIFIED'", one=True)["n"]
        self.assertEqual(np_count, rows)

    def test_active_countries_includes_seeded_regulatory_jurisdictions(self):
        codes = {c["code"] for c in cs.active_countries()}
        self.assertEqual(codes, {"NP", "IN", "AE"})

    def test_rate_dict_matches_db_default_nepal_behaviour(self):
        fy = db.current_fy_label()
        via_service, unverified_service = cs.rate_dict(1, "income_tax", fy)
        via_db, unverified_db = db.rate_dict("income_tax", fy)
        self.assertEqual(via_service.keys(), via_db.keys())
        self.assertEqual(unverified_service, unverified_db)

    def test_rate_master_rows_have_no_unbackfilled_country_code(self):
        # Every row predates country_code except the deliberately-seeded IN
        # rows (see IndiaSeedDataTests) — so the real invariant the
        # _NEW_COLUMNS backfill guarantees is "no row was left NULL/blank",
        # not "every row is NP" (that stopped being true once IN was seeded).
        total = db.query("SELECT COUNT(*) n FROM rate_master", one=True)["n"]
        backfilled = db.query(
            "SELECT COUNT(*) n FROM rate_master WHERE country_code IS NOT NULL AND country_code != ''",
            one=True)["n"]
        self.assertGreater(total, 0)
        self.assertEqual(total, backfilled)

    def test_firm_table_has_country_code_column(self):
        cols = {c["name"] for c in db.query("PRAGMA table_info(firm)")}
        self.assertIn("country_code", cols)


class ModuleApplicabilityTests(unittest.TestCase):
    def setUp(self):
        for mod in ("db", "country_service"):
            sys.modules.pop(mod, None)
        import country_service as cs_mod
        global cs
        cs = cs_mod

    def test_module_absent_from_the_map_applies_everywhere(self):
        for code in ("NP", "IN", "AE", "US"):
            self.assertTrue(cs.is_module_applicable("transfer_pricing", code))

    def test_personal_tax_not_applicable_to_uae(self):
        self.assertFalse(cs.is_module_applicable("personal_tax", "AE"))
        self.assertTrue(cs.is_module_applicable("personal_tax", "NP"))
        self.assertTrue(cs.is_module_applicable("personal_tax", "IN"))

    def test_uae_corporate_tax_only_applicable_to_uae(self):
        self.assertTrue(cs.is_module_applicable("uae_corporate_tax", "AE"))
        self.assertFalse(cs.is_module_applicable("uae_corporate_tax", "NP"))
        self.assertFalse(cs.is_module_applicable("uae_corporate_tax", "IN"))

    def test_india_specific_engines_scoped_to_india_only(self):
        for key in ("india_depreciation", "india_gst", "india_interest_penalty"):
            self.assertTrue(cs.is_module_applicable(key, "IN"))
            self.assertFalse(cs.is_module_applicable(key, "AE"))
            self.assertFalse(cs.is_module_applicable(key, "NP"))

    def test_nepal_specific_engines_scoped_to_nepal_only(self):
        for key in ("depreciation", "biz_tax", "penalty"):
            self.assertTrue(cs.is_module_applicable(key, "NP"))
            self.assertFalse(cs.is_module_applicable(key, "AE"))


class UseCountryContextTests(unittest.TestCase):
    """These are what actually make engines.py country-aware: engines call
    db.rate_dict(domain, fy, applies_to) with no country_code of their own,
    so the context set here is the only thing that changes which country's
    rows they see."""

    def setUp(self):
        self._tmpdir = tempfile.mkdtemp()
        os.environ["CA_DATA_DIR"] = self._tmpdir
        for mod in ("db", "country_service"):
            sys.modules.pop(mod, None)
        import db as db_mod
        import country_service as cs_mod
        global db, cs
        db, cs = db_mod, cs_mod
        db.init_db()
        # A distinguishing rate row for a second country, so a lookup that
        # picks up 'IN' from context returns something Nepal's rows don't have.
        db.execute(
            "INSERT INTO rate_master (domain, code, label, value_num, country_code,"
            " verification_status) VALUES ('income_tax','test_only_marker','x',42,'IN','SECONDARY')")

    def test_default_outside_any_context_is_nepal(self):
        rows = db.rates(domain="income_tax", country_code=None)
        self.assertTrue(all(r["country_code"] == "NP" for r in rows))

    def test_use_country_changes_unparameterized_lookups(self):
        with cs.use_country("IN"):
            rows = db.rates(domain="income_tax")
        codes = {r["code"] for r in rows}
        self.assertIn("test_only_marker", codes)

    def test_context_is_restored_after_the_block(self):
        with cs.use_country("IN"):
            pass
        rows = db.rates(domain="income_tax")
        self.assertTrue(all(r["country_code"] == "NP" for r in rows))

    def test_context_is_restored_even_on_exception(self):
        with self.assertRaises(ValueError):
            with cs.use_country("IN"):
                raise ValueError("boom")
        rows = db.rates(domain="income_tax")
        self.assertTrue(all(r["country_code"] == "NP" for r in rows))

    def test_explicit_country_code_overrides_the_context(self):
        with cs.use_country("IN"):
            rows = db.rates(domain="income_tax", country_code="NP")
        self.assertTrue(all(r["country_code"] == "NP" for r in rows))

    def test_use_firm_country_resolves_the_firms_own_jurisdiction(self):
        db.execute("UPDATE firm SET country_code = 'IN' WHERE id = 1")
        with cs.use_firm_country(1):
            rows = db.rates(domain="income_tax")
        codes = {r["code"] for r in rows}
        self.assertIn("test_only_marker", codes)


class NepalCalendarGatingTests(unittest.TestCase):
    """engines._bs_label — a non-Nepal firm's engine output shouldn't carry
    a Bikram Sambat date that has nothing to do with its own calendar."""

    def setUp(self):
        self._tmpdir = tempfile.mkdtemp()
        os.environ["CA_DATA_DIR"] = self._tmpdir
        for mod in ("db", "country_service", "engines"):
            sys.modules.pop(mod, None)
        import db as db_mod
        import country_service as cs_mod
        import engines as engines_mod
        global db, cs, engines
        db, cs, engines = db_mod, cs_mod, engines_mod
        db.init_db()

    def _loan(self):
        return engines.loan_schedule({
            "principal": 100000, "annual_rate": 10, "tenure_periods": 6,
            "frequency": "monthly", "repayment": "emi", "start_date_ad": "2025-07-17",
        })

    def test_nepal_context_gets_a_bs_label(self):
        with cs.use_country("NP"):
            result = self._loan()
        self.assertIsNotNone(result["summary"]["start_date_bs_approx"])

    def test_non_nepal_context_gets_no_bs_label(self):
        with cs.use_country("IN"):
            result = self._loan()
        self.assertIsNone(result["summary"]["start_date_bs_approx"])

    def test_default_outside_any_context_is_nepal_unchanged(self):
        # No use_country wrapper at all — matches every pre-existing caller
        # that predates country_service; behaviour must be exactly as before.
        result = self._loan()
        self.assertIsNotNone(result["summary"]["start_date_bs_approx"])


class IndiaSeedDataTests(unittest.TestCase):
    """The India rate_master content seeded in seed.sql — proves the seed
    rows are actually readable through the real engines under use_country('IN'),
    not just present in the table."""

    def setUp(self):
        self._tmpdir = tempfile.mkdtemp()
        os.environ["CA_DATA_DIR"] = self._tmpdir
        for mod in ("db", "country_service", "engines"):
            sys.modules.pop(mod, None)
        import db as db_mod
        import country_service as cs_mod
        import engines as engines_mod
        global db, cs, engines
        db, cs, engines = db_mod, cs_mod, engines_mod
        db.init_db()

    def test_individual_slabs_match_the_new_regime_structure(self):
        with cs.use_country("IN"):
            result = engines.personal_tax(db.current_fy_label(), "individual", 1200000)
        # 0% to 3L, 5% 3-7L, 10% 7-10L, 15% 10-12L => 0 + 20000 + 30000 + 30000
        self.assertEqual(result["tax_before_rebate"], 80000.0)

    def test_no_couple_status_seeded_for_india(self):
        # applies_to='couple' rows specifically — not db.rate_dict(...,
        # 'couple'), which also legitimately returns any applies_to='all'
        # row (loss_carry_forward_years etc.) by design, same as for Nepal.
        rows = db.query(
            "SELECT 1 FROM rate_master WHERE country_code = 'IN' AND applies_to = 'couple'")
        self.assertEqual(rows, [])

    def test_company_rate_categories_are_discovered_dynamically(self):
        rates, _ = db.rate_dict("income_tax", db.current_fy_label(), "company", country_code="IN")
        slugs = {c["slug"] for c in engines._dynamic_categories(rates, "company_rate_")}
        self.assertEqual(slugs, {"domestic_standard", "domestic_concessional", "new_manufacturing", "foreign"})

    def test_salary_tds_uses_epf_via_the_existing_pf_fallback_path(self):
        employee = {"name": "Test", "status": "individual", "months": 12,
                    "basic_monthly": 50000, "ssf_contributor": False}
        with cs.use_country("IN"):
            result = engines.salary_tds(db.current_fy_label(), employee)
        self.assertEqual(result["employer_contribution"], 72000.0)  # 12% of 600000 basic
        self.assertEqual(result["employee_contribution"], 72000.0)

    def test_tds_categories_available_for_india(self):
        rates, _ = db.rate_dict("tds", db.current_fy_label(), country_code="IN")
        categories = engines._dynamic_categories(rates, "tds_rate_")
        slugs = {c["slug"] for c in categories}
        self.assertIn("contract", slugs)
        self.assertIn("nonresident", slugs)

    def test_nonresident_tds_rate_is_deliberately_unset_not_guessed(self):
        rates, unverified = db.rate_dict("tds", db.current_fy_label(), country_code="IN")
        self.assertIsNone(rates["tds_rate_nonresident"]["value_num"])
        self.assertIn("tds.tds_rate_nonresident", unverified)

    def test_every_india_row_is_unverified(self):
        rows = db.query("SELECT verification_status FROM rate_master WHERE country_code = 'IN'")
        self.assertTrue(rows)
        self.assertTrue(all(r["verification_status"] == "UNVERIFIED" for r in rows))

    def test_vat_domain_has_no_india_rows_since_gst_is_its_own_domain(self):
        # Nepal's `vat` domain (one 'standard_rate' field) can't represent
        # GST's multi-slab shape — see india_engines.india_gst_return() and
        # its own 'gst' domain rows instead. This test guards that GST
        # content never gets force-fit into `vat` by a future change.
        rows = db.query("SELECT 1 FROM rate_master WHERE country_code = 'IN' AND domain = 'vat'")
        self.assertEqual(rows, [])

    def test_depreciation_and_penalty_domains_do_have_india_rows(self):
        # Depreciation (Sec 32 blocks) and penalty (234A/B/C/F interest/fee)
        # DO have real engine-shape support now — see india_engines.py and
        # PROJECT_STATE.md's "India engine-shape work" entry. This replaces
        # an earlier test that (correctly, at the time) asserted these were
        # empty; superseded once the engines existed.
        for domain in ("depreciation", "penalty"):
            rows = db.query("SELECT 1 FROM rate_master WHERE country_code = 'IN' AND domain = ?", (domain,))
            self.assertTrue(rows, f"expected IN rows in domain={domain!r}")


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