import json
from pathlib import Path
import tempfile
import unittest

from experiment import (check_summary, create_store, expected_values, inventory,
                        read_summary, require, run, scalar, store_bytes)


class ConsumerTests(unittest.TestCase):
    def test_guard_is_not_an_assert(self):
        with self.assertRaises(RuntimeError):
            require(False, "deliberate test failure")

    def test_json_missing_values_are_standard_nulls(self):
        self.assertEqual(json.dumps([scalar(float("nan")), scalar(1)], allow_nan=False),
                         "[null, 1.0]")

    def test_invalid_repetition_counts(self):
        for value in (0, -1, True, 1.5):
            with self.subTest(value=value), self.assertRaises(RuntimeError):
                run(value)

    def test_documented_false_keeps_cf_attribute(self):
        self.assertEqual(expected_values(3, 0, False, "deleted"),
                         [10, 11, 12, 13, None, None, None, None])

    def test_public_option_and_backend_control_diverge(self):
        for version, policy, public_mean, backend_mean in (
            (2, False, 11.5, 5.75), (3, True, 5.75, 11.5)
        ):
            with self.subTest(version=version), tempfile.TemporaryDirectory() as d:
                path = Path(d) / "store"
                create_store(path, version, state="deleted")
                self.assertEqual(read_summary(path, policy)["default_mean"], public_mean)
                self.assertEqual(read_summary(path, policy, route="backend_control")["default_mean"],
                                 backend_mean)

    def test_explicit_cf_zero_still_masks_with_false(self):
        with tempfile.TemporaryDirectory() as d:
            path = Path(d) / "store"
            create_store(path, 3, cf_fill=0, state="deleted")
            self.assertEqual(read_summary(path, False)["nonmissing_count"], 4)

    def test_disabling_decode_preserves_raw_fill(self):
        with tempfile.TemporaryDirectory() as d:
            path = Path(d) / "store"
            create_store(path, 2, cf_fill=0, state="deleted")
            actual = read_summary(path, mask_and_scale=False)
            self.assertEqual(actual["nonmissing_count"], 8)
            self.assertEqual(actual["default_mean"], 5.75)

    def test_written_zero_is_not_proof_of_absence(self):
        for version, count in ((2, 7), (3, 8)):
            with self.subTest(version=version), tempfile.TemporaryDirectory() as d:
                path = Path(d) / "store"
                self.assertEqual(create_store(path, version, values=list(range(8))), [])
                self.assertEqual(read_summary(path)["nonmissing_count"], count)

    def test_sparse_and_deleted_stores_are_byte_identical(self):
        for version in (2, 3):
            with self.subTest(version=version), tempfile.TemporaryDirectory() as d:
                deleted, sparse = Path(d) / "deleted", Path(d) / "sparse"
                missing = create_store(deleted, version, state="deleted")
                create_store(sparse, version, state="sparse")
                self.assertEqual(len(missing), 1)
                self.assertEqual(store_bytes(deleted), store_bytes(sparse))
                self.assertEqual(inventory(deleted), inventory(sparse))

    def test_summary_validation_rejects_wrong_denominator(self):
        with tempfile.TemporaryDirectory() as d:
            path = Path(d) / "store"
            create_store(path, 2, state="deleted")
            actual = read_summary(path)
            actual["nonmissing_count"] = 8
            with self.assertRaises(RuntimeError):
                check_summary(actual, [10, 11, 12, 13, None, None, None, None])


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