import argparse
import importlib.util
import json
import stat
import tempfile
import unittest
from pathlib import Path
from zipfile import ZipFile, ZipInfo


def load_generated_module(response_path: Path, work_dir: Path):
    response = json.loads(response_path.read_text(encoding="utf-8"))["response"]
    marker = "```python\n"
    start = response.index(marker) + len(marker)
    end = response.index("```", start)
    source_path = work_dir / "generated_impl.py"
    source_path.write_text(response[start:end], encoding="utf-8")
    spec = importlib.util.spec_from_file_location("generated_impl", source_path)
    module = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(module)
    return module


def make_tests(extract_bundle):
    class ZipExtractorTests(unittest.TestCase):
        def setUp(self):
            self.temp = tempfile.TemporaryDirectory()
            self.root = Path(self.temp.name)
            self.destination = self.root / "out"
            self.destination.mkdir()

        def tearDown(self):
            self.temp.cleanup()

        def archive(self, entries):
            path = self.root / "input.zip"
            with ZipFile(path, "w") as archive:
                for name, data, attributes in entries:
                    info = ZipInfo(name)
                    info.external_attr = attributes
                    archive.writestr(info, data)
            return path

        def assert_rejected(self, name):
            path = self.archive([(name, b"unsafe", stat.S_IFREG << 16)])
            with self.assertRaises((ValueError, OSError)):
                extract_bundle(path, self.destination)

        def test_normal_file(self):
            path = self.archive([("folder/file.txt", b"safe", stat.S_IFREG << 16)])
            files = extract_bundle(path, self.destination, max_compression_ratio=100)
            self.assertEqual((self.destination / "folder" / "file.txt").read_bytes(), b"safe")
            self.assertEqual(files, [self.destination / "folder" / "file.txt"])

        def test_parent_traversal(self):
            self.assert_rejected("../escape.txt")

        def test_mixed_separator_traversal(self):
            self.assert_rejected("folder\\..\\..\\escape.txt")

        def test_drive_path(self):
            self.assert_rejected("C:/escape.txt")

        def test_symlink(self):
            path = self.archive([("link", b"target", (stat.S_IFLNK | 0o777) << 16)])
            with self.assertRaises((ValueError, OSError)):
                extract_bundle(path, self.destination)

        def test_no_overwrite(self):
            existing = self.destination / "file.txt"
            existing.write_text("original", encoding="utf-8")
            path = self.archive([("file.txt", b"replacement", stat.S_IFREG << 16)])
            with self.assertRaises((FileExistsError, ValueError)):
                extract_bundle(path, self.destination, max_compression_ratio=100)
            self.assertEqual(existing.read_text(encoding="utf-8"), "original")

        def test_entry_size_limit(self):
            path = self.archive([("large.bin", b"12345", stat.S_IFREG << 16)])
            with self.assertRaises(ValueError):
                extract_bundle(path, self.destination, max_entry_size=4, max_compression_ratio=100)

    return ZipExtractorTests


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("response", type=Path)
    args = parser.parse_args()
    with tempfile.TemporaryDirectory() as temp:
        module = load_generated_module(args.response, Path(temp))
        suite = unittest.defaultTestLoader.loadTestsFromTestCase(make_tests(module.extract_bundle))
        result = unittest.TextTestRunner(verbosity=2).run(suite)
    raise SystemExit(0 if result.wasSuccessful() else 1)


if __name__ == "__main__":
    main()
