"""Regression checks for the SIEVE teaching artifact; run with Python 3.12."""
import importlib.util
import subprocess
import sys
import unittest
from pathlib import Path
from unittest.mock import patch

sys.dont_write_bytecode = True
PATH = Path(__file__).with_name("sieve-traces.py")
spec = importlib.util.spec_from_file_location("sieve_traces", PATH)
model = importlib.util.module_from_spec(spec)
spec.loader.exec_module(model)


class ReplayTests(unittest.TestCase):
    def test_empty_trace(self):
        self.assertEqual(model.replay("", 3, "SIEVE")["outcomes"], "")

    def test_published_rows(self):
        for _, refs, capacity in model.CASES:
            self.assertEqual(model.replay(refs, capacity, "SIEVE")["outcomes"],
                             model.sieve_oracle(refs, capacity)["outcomes"])

    def test_extended_differential(self):
        self.assertEqual(model.check(), 67723)

    def test_invalid_inputs(self):
        for value in (None, [], 0, "a", "A B", "A0"):
            with self.assertRaises(ValueError):
                model.replay(value, 3, "SIEVE")
        for capacity in (0, -1, True, 2.5, "3"):
            with self.assertRaises(ValueError):
                model.replay("A", capacity, "SIEVE")
        with self.assertRaises(ValueError):
            model.replay("A", 3, "other")

    def test_wrong_version_fails_with_and_without_optimization(self):
        code = ("import runpy; m=runpy.run_path(" + repr(str(PATH)) + "); "
                "m['importlib'].metadata.version=lambda _: 'wrong'; m['check']()")
        for flags in ([], ["-O"]):
            result = subprocess.run([sys.executable, *flags, "-c", code],
                                    capture_output=True, text=True)
            self.assertNotEqual(result.returncode, 0)
            self.assertIn("version mismatch", result.stderr)

    def test_bad_oracle_fails_even_under_optimization(self):
        code = ("import runpy; m=runpy.run_path(" + repr(str(PATH)) + "); "
                "m['check'].__globals__['sieve_oracle']="
                "lambda refs, cap: {'outcomes': 'H'*len(refs)}; m['check']()")
        result = subprocess.run([sys.executable, "-O", "-c", code],
                                capture_output=True, text=True)
        self.assertNotEqual(result.returncode, 0)
        self.assertIn("differential mismatch", result.stderr)

    def test_direct_oracle_failure(self):
        with patch.object(model, "sieve_oracle", return_value={"outcomes": "wrong"}):
            with self.assertRaisesRegex(AssertionError, "differential mismatch"):
                model.check()


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