#!/usr/bin/env python3
# SPDX-License-Identifier: CC0-1.0
"""Regression checks for the downloadable zero-copy dataset generator."""
import contextlib
import copy
import importlib.util
import io
import json
from pathlib import Path
import shutil
import subprocess
import sys
import tempfile
import unittest
from unittest.mock import patch

HERE = Path(__file__).resolve().parent
spec = importlib.util.spec_from_file_location("dataset", HERE / "build-zero-copy-dataset.py")
dataset = importlib.util.module_from_spec(spec)
spec.loader.exec_module(dataset)
FILES = ("zero-copy-lifetime.json", "zero-copy-lifetime.py", "tee-source-lifetime.json",
         "tee-source-lifetime.py", "zero-copy-sources.json", "test_zero_copy_lifetime.py",
         "test_tee_lifetime.py", "build-zero-copy-dataset.py", "test_zero_copy_dataset.py")


class DatasetTests(unittest.TestCase):
    def setup_copy(self, root):
        research = Path(root) / "research"
        research.mkdir()
        for name in FILES:
            shutil.copy2(HERE / name, research / name)
        return research

    def reject(self, mutation, filename="zero-copy-lifetime.json"):
        with tempfile.TemporaryDirectory() as root:
            research = self.setup_copy(root)
            path = research / filename
            raw = json.loads(path.read_text())
            mutation(raw)
            path.write_text(json.dumps(raw))
            output = Path(root) / "data"
            (output / "datasets").mkdir(parents=True)
            sentinel = output / "zero-copy-source-lifetime.csv"
            sentinel.write_text("unchanged")
            with patch.object(dataset, "HERE", research), contextlib.redirect_stdout(io.StringIO()):
                with self.assertRaises(ValueError):
                    dataset.generate()
            self.assertEqual(sentinel.read_text(), "unchanged", "invalid input wrote outputs")

    def test_rejects_corrupted_payload_hash(self):
        self.reject(lambda raw: raw["observations"][0]["received"].update(sha256="0" * 64))

    def test_rejects_duplicate_or_missing_case(self):
        self.reject(lambda raw: raw["observations"].__setitem__(1, copy.deepcopy(raw["observations"][0])))
        self.reject(lambda raw: raw["observations"].pop())

    def test_rejects_inconsistent_byte_counts(self):
        self.reject(lambda raw: raw["observations"][0].update(enqueue_return_bytes=1))
        self.reject(lambda raw: raw["observations"][0]["path_after"].update(bytes=1))

    def test_rejects_false_hash_comparison(self):
        self.reject(lambda raw: raw["observations"][0].update(received_matches_pretransfer_hash=False))

    def test_rejects_invalid_repetition(self):
        self.reject(lambda raw: raw["observations"][0].update(repetition=True))
        self.reject(lambda raw: raw.update(repetitions=0))

    def test_rejects_supplement_corruption(self):
        name = "tee-source-lifetime.json"
        self.reject(lambda raw: raw["observations"][0].update(tee_return_bytes=1), name)
        self.reject(lambda raw: raw["observations"][0].update(consumer_hashes_equal=True), name)

    def test_downloaded_copy_roundtrip(self):
        with tempfile.TemporaryDirectory() as root:
            research = self.setup_copy(root)
            subprocess.run([sys.executable, "-B", "-OO", str(research / "build-zero-copy-dataset.py")],
                           cwd=root, check=True, capture_output=True, text=True)
            meta = json.loads((Path(root) / "data/datasets/zero-copy-source-lifetime.json").read_text())
            self.assertEqual(meta["rowCount"], 40)
            self.assertEqual(meta["supplement"]["rowCount"], 10)
            self.assertEqual(meta["sha256"]["raw"], dataset.sha(HERE / "zero-copy-lifetime.json"))
            self.assertEqual(meta["validationTestsPath"], "/research/test_zero_copy_dataset.py")


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