"""Independent examples, exhaustive small cases, and deterministic differential checks."""

from copy import deepcopy
from itertools import combinations, product
from math import comb, lcm
import random
import unittest

from practice.math_lab import (
    EXAMPLE, MOD, badge_summary, booth_area2, build_report, choose_sessions,
    delivery_capacity, demand_report, equipment_budget, packing_unit,
    registration_code, shortest_busy_window, skills_report, synchronized_launch,
    team_count,
)


class MathLabTests(unittest.TestCase):
    def test_complete_worked_example(self):
        report = build_report(EXAMPLE)
        self.assertEqual(report["badge"], {"digits": 4, "digit_sum": 7, "even": True})
        self.assertEqual(report["packing_unit"], 2)
        # 1204**4 = 2_101_386_547_456; reduce by 1_000_000_007.
        self.assertEqual(report["registration_code"], 386532749)
        self.assertEqual(report["team_count_mod"], 6)
        self.assertEqual(report["skills"], {"available_mask": 7, "missing_mask": 0, "ready": True})
        self.assertEqual(report["demand"], {"daily": [2, 6, 4, 6], "total": 18, "range_totals": [8, 16]})
        self.assertEqual(report["shortest_busy_window"], 2)
        self.assertEqual(report["delivery_capacity"], 10)
        self.assertEqual(report["session_indices"], [0, 2, 3])
        self.assertEqual(report["booth_area2"], 24)
        self.assertEqual(report["launch_time"], 22)
        self.assertEqual(report["equipment"]["spent"], 11)
        self.assertEqual(report["equipment"]["remaining"], 0)
        self.assertEqual(sum(EXAMPLE["equipment_costs"][i] for i in report["equipment"]["indices"]), 11)
        self.assertTrue(report["can_dispatch"])

    def test_badges_zeroes_and_large_decimal(self):
        self.assertEqual(badge_summary("0000"), {"digits": 4, "digit_sum": 0, "even": True})
        self.assertEqual(badge_summary("007"), {"digits": 3, "digit_sum": 7, "even": False})
        identifier = "1234567890" * 100
        self.assertEqual(registration_code(identifier, 19), pow(int(identifier), 19, MOD))
        self.assertEqual(registration_code("000", 2), 0)

    def test_counts_and_masks(self):
        for n in range(41):
            for k in range(n + 1):
                self.assertEqual(team_count(n, k), comb(n, k) % MOD)
        self.assertEqual(team_count(2000, 1000), comb(2000, 1000) % MOD)
        self.assertEqual(packing_unit([12, 18, 30]), 6)
        self.assertEqual(packing_unit([1]), 1)
        self.assertEqual(skills_report([1, 2], 7), {"available_mask": 3, "missing_mask": 4, "ready": False})
        self.assertTrue(skills_report([0], 0)["ready"])

    def test_difference_and_prefix_against_direct_updates(self):
        rng = random.Random(105)
        for _ in range(120):
            n = rng.randint(1, 12)
            base = [rng.randint(1, 9) for _ in range(n)]
            original = list(base)
            expected = list(base)
            updates = []
            for _ in range(rng.randint(0, 12)):
                left = rng.randrange(n)
                right = rng.randint(left + 1, n)
                extra = rng.randint(0, 6)
                updates.append([left, right, extra])
                for i in range(left, right):
                    expected[i] += extra
            queries = [(left, right) for left in range(n) for right in range(left + 1, n + 1)]
            actual = demand_report(base, updates, queries)
            self.assertEqual(actual["daily"], expected)
            self.assertEqual(actual["range_totals"], [sum(expected[l:r]) for l, r in queries])
            self.assertEqual(actual["total"], sum(expected))
            self.assertEqual(base, original)

    def test_windows_against_all_subarrays(self):
        for n in range(1, 5):
            for values in product(range(1, 4), repeat=n):
                for target in range(1, sum(values) + 2):
                    lengths = [r - l for l in range(n) for r in range(l + 1, n + 1)
                               if sum(values[l:r]) >= target]
                    expected = min(lengths, default=0)
                    self.assertEqual(shortest_busy_window(values, target), expected)

    def test_capacity_against_all_partitions(self):
        rng = random.Random(107)
        for _ in range(120):
            n = rng.randint(1, 8)
            values = [rng.randint(1, 9) for _ in range(n)]
            days = rng.randint(1, n)
            candidates = []
            for cut_count in range(days):
                for cuts in combinations(range(1, n), cut_count):
                    boundaries = (0,) + cuts + (n,)
                    candidates.append(max(sum(values[a:b]) for a, b in zip(boundaries, boundaries[1:])))
            self.assertEqual(delivery_capacity(values, days), min(candidates))

    def test_greedy_sessions_against_all_subsets(self):
        rng = random.Random(108)
        for _ in range(100):
            sessions = []
            for _ in range(rng.randint(0, 8)):
                start = rng.randrange(10)
                sessions.append((start, rng.randint(start + 1, 11)))
            best = 0
            for mask in range(1 << len(sessions)):
                subset = sorted(sessions[i] for i in range(len(sessions)) if mask & (1 << i))
                if all(a[1] <= b[0] for a, b in zip(subset, subset[1:])):
                    best = max(best, len(subset))
            actual = choose_sessions(sessions)
            self.assertEqual(len(actual), best)
            self.assertEqual(len(actual), len(set(actual)))
            self.assertTrue(all(sessions[a][1] <= sessions[b][0] for a, b in zip(actual, actual[1:])))

    def test_geometry_known_shapes_and_degeneracy(self):
        cases = [([], 0), ([(1, 2)], 0), ([(0, 0), (1, 1), (2, 2)], 0),
                 ([(0, 0), (1, 0), (0, 1)], 1),
                 ([(0, 2), (2, 0), (0, -2), (-2, 0), (0, 0)], 16),
                 ([(0, 0), (4, 0), (4, 3), (0, 3), (2, 1), (0, 0)], 24)]
        for points, expected in cases:
            self.assertEqual(booth_area2(points), expected)
            self.assertEqual(booth_area2(list(reversed(points))), expected)
            self.assertEqual(booth_area2([(x - 50, y + 19) for x, y in points]), expected)

    def test_generalized_crt_against_exhaustive_search(self):
        for modulus1 in range(1, 8):
            for modulus2 in range(1, 8):
                cycle = lcm(modulus1, modulus2)
                for remainder1 in range(modulus1):
                    for remainder2 in range(modulus2):
                        for earliest in (0, 11, 100):
                            expected = next((t for t in range(earliest, earliest + cycle)
                                             if t % modulus1 == remainder1 and t % modulus2 == remainder2), None)
                            actual = synchronized_launch([(remainder1, modulus1), (remainder2, modulus2)], earliest)
                            self.assertEqual(actual, expected)
        self.assertEqual(synchronized_launch([], 19), 19)
        self.assertEqual(synchronized_launch([(2, 3), (3, 5), (2, 7)], 24), 128)

    def test_equipment_against_exhaustive_subsets(self):
        rng = random.Random(111)
        for _ in range(120):
            costs = [rng.randint(0, 20) for _ in range(rng.randint(0, 12))]
            budget = rng.randint(0, 50)
            totals = [sum(costs[i] for i in range(len(costs)) if mask & (1 << i))
                      for mask in range(1 << len(costs))]
            expected = max(total for total in totals if total <= budget)
            result = equipment_budget(costs, budget)
            self.assertEqual(result["spent"], expected)
            self.assertEqual(result["remaining"], budget - expected)
            self.assertEqual(len(result["indices"]), len(set(result["indices"])))
            self.assertEqual(sum(costs[i] for i in result["indices"]), expected)
        self.assertEqual(equipment_budget([1] * 32, 17)["spent"], 17)

    def test_dispatch_requires_all_three_conditions_and_preserves_input(self):
        data = deepcopy(EXAMPLE)
        snapshot = deepcopy(data)
        build_report(data)
        self.assertEqual(data, snapshot)
        for key, value in (("required_mask", 15), ("booths", [[0, 0]]),
                           ("gate_cycles", [[0, 2], [1, 2]])):
            altered = deepcopy(data)
            altered[key] = value
            self.assertFalse(build_report(altered)["can_dispatch"])

    def test_invalid_input_contract(self):
        cases = [("batch_id", ""), ("batch_id", "১২"), ("batch_id", "1" * 1001),
                 ("base_demand", []), ("base_demand", [0]), ("base_demand", [True]),
                 ("updates", [[1, 5, 2]]), ("updates", [[1, 2, -1]]),
                 ("queries", [[2, 2]]), ("queries", [[-1, 1]]),
                 ("volunteer_masks", []), ("volunteer_masks", [1 << 20]),
                 ("required_mask", -1), ("team_size", 5), ("team_size", -1),
                 ("window_target", 0), ("shipping_days", 0), ("shipping_days", 5),
                 ("sessions", [[4, 4]]), ("booths", [[0, "x"]]),
                 ("gate_cycles", [[0, 0]]), ("gate_cycles", [[3, 3]]),
                 ("earliest_launch", -1), ("equipment_costs", [1] * 33),
                 ("equipment_costs", [-1]), ("budget", -1)]
        for key, value in cases:
            data = deepcopy(EXAMPLE)
            data[key] = value
            with self.subTest(key=key, value=value), self.assertRaises(ValueError):
                build_report(data)
        for data in (None, {}, {**EXAMPLE, "typo": 1}):
            with self.assertRaises(ValueError):
                build_report(data)


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