import random
import unittest

from practice.hashing_lab import audit_ledger, count_target_batches, first_target_pair


class LedgerTests(unittest.TestCase):
    def test_worked_example(self):
        records = [("t1", "A", 4), ("t2", "B", -1), ("t1", "A", 4), ("t3", "A", -3), ("t4", "B", 4)]
        result = audit_ledger(records, 3)
        self.assertEqual(result["records"], [records[0], records[1], records[3], records[4]])
        self.assertEqual(result["balances"], {"A": 1, "B": 3})
        self.assertEqual(result["pair"], ("t1", "t2"))
        self.assertEqual(result["batch_count"], 1)

    def test_empty(self):
        self.assertEqual(audit_ledger([]), {"records": [], "balances": {}, "pair": None, "batch_count": 0})

    def test_conflicting_replay(self):
        for second in [("x", "a", 2), ("x", "b", 1)]:
            with self.assertRaises(ValueError):
                audit_ledger([("x", "a", 1), second])

    def test_same_amount_different_transactions(self):
        result = audit_ledger([("x", "a", 2), ("y", "a", 2)], 4)
        self.assertEqual(result["pair"], ("x", "y"))
        self.assertEqual(result["balances"], {"a": 4})

    def test_single_record_cannot_pair_with_itself(self):
        self.assertIsNone(audit_ledger([("x", "a", 2)], 4)["pair"])

    def test_zeros_and_nonempty_batches(self):
        records = [(str(i), "a", 0) for i in range(4)]
        self.assertEqual(audit_ledger(records)["batch_count"], 10)
        self.assertEqual(audit_ledger(records)["pair"], ("0", "1"))

    def test_negative_target(self):
        self.assertEqual(audit_ledger([("x", "a", -2), ("y", "a", -3)], -5)["batch_count"], 1)

    def test_first_pair_tie_break(self):
        records = [("a", "x", 2), ("b", "x", 2), ("c", "x", 3)]
        self.assertEqual(first_target_pair(records, 5), ("a", "c"))

    def test_invalid_record(self):
        for record in [("", "a", 1), ("x", "", 1), ("x", "a", "1")]:
            with self.assertRaises(ValueError):
                audit_ledger([record])

    def test_random_against_quadratic_audit(self):
        rng = random.Random(41)
        for _ in range(180):
            values = [rng.randrange(-3, 4) for _ in range(rng.randrange(9))]
            records = [(str(i), "A", value) for i, value in enumerate(values)]
            target = rng.randrange(-4, 5)
            count = sum(sum(values[i:j]) == target for i in range(len(values)) for j in range(i + 1, len(values) + 1))
            self.assertEqual(count_target_batches(records, target), count)
            pairs = [(j, i) for j in range(len(values)) for i in range(j) if values[i] + values[j] == target]
            expected = None if not pairs else tuple(str(index) for index in min(pairs)[::-1])
            self.assertEqual(first_target_pair(records, target), expected)


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