import random
import unittest

from practice.arrays_lab import (best_score_span, longest_unique_span,
                                merge_sessions, session_report)


class SessionReportTests(unittest.TestCase):
    def test_worked_example(self):
        result = session_report([(1, "A", 3), (3, "B", -2)],
                                [(2, "C", 4), (4, "A", 5)], [(0, 3), (2, 4)])
        self.assertEqual(result["sessions"], [(1, "A", 3), (2, "C", 4), (3, "B", -2), (4, "A", 5)])
        self.assertEqual(result["range_scores"], [5, 3])
        self.assertEqual(result["unique_span"], (0, 3))
        self.assertEqual(result["best_score"], {"score": 10, "span": (0, 4)})

    def test_empty(self):
        self.assertEqual(session_report([], [], [(0, 0)]), {
            "sessions": [], "range_scores": [0], "unique_span": (0, 0), "best_score": None})

    def test_stable_merge(self):
        self.assertEqual(merge_sessions([(1, "L", 0)], [(1, "R", 0)]),
                         [(1, "L", 0), (1, "R", 0)])

    def test_no_input_mutation(self):
        batch = [(1, "x", 2)]
        session_report(batch, [], [(0, 1)])
        self.assertEqual(batch, [(1, "x", 2)])

    def test_negative_scores(self):
        self.assertEqual(best_score_span([-3, -1, -1]), {"score": -1, "span": (1, 2)})

    def test_ties(self):
        self.assertEqual(best_score_span([0, 1, -1, 1]), {"score": 1, "span": (0, 2)})
        self.assertEqual(longest_unique_span("abba"), (0, 2))

    def test_invalid_query(self):
        for query in [(-1, 0), (1, 0), (0, 2)]:
            with self.assertRaises(ValueError):
                session_report([(1, "x", 1)], [], [query])

    def test_unsorted_and_invalid_session(self):
        for batch in [[(2, "a", 1), (1, "b", 2)], [(1, "", 2)]]:
            with self.assertRaises(ValueError):
                session_report(batch, [])

    def test_random_against_exhaustive_spans(self):
        rng = random.Random(29)
        for _ in range(150):
            values = [rng.randrange(-4, 5) for _ in range(rng.randrange(1, 9))]
            candidates = [(sum(values[i:j]), i, j) for i in range(len(values))
                          for j in range(i + 1, len(values) + 1)]
            score, start, end = min(candidates, key=lambda item: (-item[0], item[1], item[2]))
            self.assertEqual(best_score_span(values), {"score": score, "span": (start, end)})
            tags = [rng.choice("abcd") for _ in values]
            spans = [(i, j) for i in range(len(tags)) for j in range(i + 1, len(tags) + 1)
                     if len(set(tags[i:j])) == j - i]
            self.assertEqual(longest_unique_span(tags), min(spans, key=lambda span: (span[0] - span[1], span[0])))


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