import itertools
import random
import unittest

from practice.recursion_lab import count_plans, enumerate_plans, plan_workshop


class WorkshopTests(unittest.TestCase):
    def test_worked_example(self):
        items = [("A", 2, 2, "math"), ("B", 1, 1, "code"), ("C", 3, 1, "code")]
        self.assertEqual(plan_workshop(items, 5), {"count": 3, "plans": [("A", "B", "A"), ("A", "C"), ("C", "A")]})

    def test_empty_budget_has_one_empty_plan(self):
        self.assertEqual(plan_workshop([], 0), {"count": 1, "plans": [()]})

    def test_empty_inventory(self):
        self.assertEqual(plan_workshop([], 1), {"count": 0, "plans": []})

    def test_identical_copies_not_duplicate_plans(self):
        items = [("A", 1, 3, "a"), ("B", 1, 2, "b")]
        self.assertEqual(enumerate_plans(items, 3), [("A", "B", "A"), ("B", "A", "B")])

    def test_same_topic_is_forbidden_even_different_names(self):
        self.assertEqual(count_plans([("A", 1, 1, "same"), ("B", 1, 1, "same")], 2), 0)

    def test_order_matters(self):
        self.assertEqual(count_plans([("A", 1, 1, "a"), ("B", 1, 1, "b")], 2), 2)

    def test_not_enough_duration(self):
        self.assertEqual(count_plans([("A", 2, 1, "a")], 3), 0)

    def test_restores_state_and_sorts_output(self):
        items = [("B", 1, 1, "b"), ("A", 1, 1, "a")]
        first = enumerate_plans(items, 2)
        self.assertEqual(first, [("A", "B"), ("B", "A")])
        self.assertEqual(enumerate_plans(items, 2), first)
        self.assertEqual(items[0][0], "B")

    def test_invalid_inputs(self):
        for items, budget in [([("A", 0, 1, "a")], 1), ([("A", 1, 9, "a")], 1),
                              ([("A", 1, 1, "a"), ("A", 2, 1, "b")], 2), ([], -1)]:
            with self.assertRaises(ValueError):
                plan_workshop(items, budget)

    def test_random_against_labeled_copy_permutations(self):
        rng = random.Random(12)
        for _ in range(60):
            items = [(chr(65 + i), rng.randrange(1, 4), rng.randrange(1, 3), str(rng.randrange(2)))
                     for i in range(rng.randrange(1, 4))]
            expanded = [item for item in items for _ in range(item[2])]
            budget = rng.randrange(7)
            expected = set()
            for size in range(len(expanded) + 1):
                for chosen in itertools.permutations(range(len(expanded)), size):
                    sequence = [expanded[index] for index in chosen]
                    if (sum(item[1] for item in sequence) == budget
                            and all(sequence[i][3] != sequence[i + 1][3] for i in range(len(sequence) - 1))):
                        expected.add(tuple(item[0] for item in sequence))
            result = plan_workshop(items, budget)
            self.assertEqual(result["plans"], sorted(expected))
            self.assertEqual(result["count"], len(expected))


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