#!/usr/bin/env python3
"""Contracts for deterministic paired plans and random substreams."""

from pathlib import Path
import sys
import unittest

import numpy as np


CODE_ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(CODE_ROOT))

from experiments.plan import build_trial_plan, validate_trial_plan  # noqa: E402
from experiments.rng import generator_from_record, named_seed_record  # noqa: E402
from experiments.schema import ContractError  # noqa: E402


def example_specification():
    return {
        "study_id": "g0c_bilateral_contract",
        "split": "calibration",
        "root_seed": 20260727,
        "replicates": 2,
        "methods": ["proposed", "direct"],
        "trajectories": [
            {
                "trajectory_id": "contact_probe_001",
                "family": "approach_contact_return",
                "parameters": {"amplitude": 0.1},
            }
        ],
        "factors": {"delay_ms": [0, 80], "stiffness_N_per_m": [500]},
    }


class ExperimentPlanContractTest(unittest.TestCase):
    def test_plan_is_deterministic_and_paired(self):
        first = build_trial_plan(example_specification())
        second = build_trial_plan(example_specification())
        self.assertEqual(first, second)
        self.assertEqual(first["pair_count"], 4)
        self.assertEqual(first["trial_count"], 8)

        by_pair = {}
        for trial in first["trials"]:
            by_pair.setdefault(trial["pair_id"], []).append(trial)
        for pair in by_pair.values():
            self.assertEqual(len(pair), 2)
            self.assertEqual(pair[0]["seeds"], pair[1]["seeds"])
            self.assertEqual(pair[0]["trajectory"], pair[1]["trajectory"])
            self.assertEqual(pair[0]["factors"], pair[1]["factors"])
            self.assertNotEqual(
                pair[0]["method"]["method_id"],
                pair[1]["method"]["method_id"],
            )

    def test_named_streams_are_order_independent_and_distinct(self):
        forward = named_seed_record(
            9,
            {"pair": 3},
            ["trajectory", "sensor", "network"],
        )
        reverse = named_seed_record(
            9,
            {"pair": 3},
            ["network", "sensor", "trajectory"],
        )
        self.assertEqual(forward, reverse)
        self.assertEqual(len({tuple(value) for value in forward.values()}), 3)

        generator_a = generator_from_record(forward, "trajectory")
        generator_b = generator_from_record(reverse, "trajectory")
        np.testing.assert_array_equal(
            generator_a.integers(0, 2**31, size=16),
            generator_b.integers(0, 2**31, size=16),
        )

    def test_plan_hash_detects_mutation(self):
        plan = build_trial_plan(example_specification())
        plan["trials"][0]["factors"]["delay_ms"] = 999
        with self.assertRaises(ContractError):
            validate_trial_plan(plan)


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