#!/usr/bin/env python3
"""H2 data pairing and conditioning-stratum regression tests."""

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.executors import execute_h2_synthetic  # noqa: E402
from experiments.plan import build_trial_plan, load_document  # noqa: E402


def h2_pairing_specification():
    return {
        "study_id": "h2_pairing_contract",
        "split": "calibration",
        "root_seed": 37,
        "replicates": 2,
        "methods": ["scaled_dls", "undamped_svd"],
        "trajectories": [
            {
                "trajectory_id": "short_dynamic_wrench",
                "family": "synthetic_dynamic",
                "sample_count": 12,
            }
        ],
        "factors": {
            "characteristic_length_m": [0.2, 0.4],
            "damping": [0.02, 0.05],
            "h2_data_root_seed": [1701],
            "min_scaled_singular": [0.02],
            "model_error_std": [0.02],
            "operational_min_scaled_singular": [0.05],
            "torque_noise_std_Nm": [0.002, 0.02],
            "truth_characteristic_length_m": [0.3],
        },
    }


def select_trial(
    plan,
    *,
    replicate,
    method,
    characteristic_length,
    damping,
    noise,
):
    matches = [
        trial
        for trial in plan["trials"]
        if trial["replicate"] == replicate
        and trial["method"]["method_id"] == method
        and trial["factors"]["characteristic_length_m"]
        == characteristic_length
        and trial["factors"]["damping"] == damping
        and trial["factors"]["torque_noise_std_Nm"] == noise
    ]
    if len(matches) != 1:
        raise AssertionError(f"expected one H2 trial, found {len(matches)}")
    return matches[0]


class H2SyntheticPairingTest(unittest.TestCase):
    def test_algorithm_candidates_reuse_identical_physical_data(self):
        plan = build_trial_plan(h2_pairing_specification())
        first_trial = select_trial(
            plan,
            replicate=0,
            method="scaled_dls",
            characteristic_length=0.2,
            damping=0.02,
            noise=0.002,
        )
        second_trial = select_trial(
            plan,
            replicate=0,
            method="undamped_svd",
            characteristic_length=0.4,
            damping=0.05,
            noise=0.002,
        )
        # The general experiment pair changes with ell/damping.  H2's explicit
        # physical-data group must still bind both candidates to the same data.
        self.assertNotEqual(first_trial["pair_id"], second_trial["pair_id"])
        self.assertNotEqual(first_trial["seeds"], second_trial["seeds"])

        first = execute_h2_synthetic(first_trial)
        second = execute_h2_synthetic(second_trial)
        self.assertEqual(
            first.metadata["h2_data_group_id"],
            second.metadata["h2_data_group_id"],
        )
        self.assertEqual(
            first.metadata["h2_data_seed_record"],
            second.metadata["h2_data_seed_record"],
        )
        self.assertNotEqual(
            first.metadata["calibration_id"],
            second.metadata["calibration_id"],
            "scanned estimator candidates must not share a frozen-looking ID",
        )
        for field in (
            "wrench_reference",
            "qd_slave",
            "jacobian_truth",
            "jacobian_estimator",
            "sensor_noise_Nm",
            "tau_residual_raw",
        ):
            np.testing.assert_array_equal(
                first.samples[field], second.samples[field]
            )
        self.assertFalse(
            np.array_equal(
                first.samples["wrench_estimated"],
                second.samples["wrench_estimated"],
            )
        )

    def test_replicates_and_physical_factor_cells_get_distinct_data(self):
        plan = build_trial_plan(h2_pairing_specification())
        baseline = execute_h2_synthetic(
            select_trial(
                plan,
                replicate=0,
                method="scaled_dls",
                characteristic_length=0.2,
                damping=0.02,
                noise=0.002,
            )
        )
        next_replicate = execute_h2_synthetic(
            select_trial(
                plan,
                replicate=1,
                method="scaled_dls",
                characteristic_length=0.2,
                damping=0.02,
                noise=0.002,
            )
        )
        different_noise = execute_h2_synthetic(
            select_trial(
                plan,
                replicate=0,
                method="scaled_dls",
                characteristic_length=0.2,
                damping=0.02,
                noise=0.02,
            )
        )
        self.assertNotEqual(
            baseline.metadata["h2_data_group_id"],
            next_replicate.metadata["h2_data_group_id"],
        )
        self.assertNotEqual(
            baseline.metadata["h2_data_group_id"],
            different_noise.metadata["h2_data_group_id"],
        )
        self.assertFalse(
            np.array_equal(
                baseline.samples["jacobian_truth"],
                next_replicate.samples["jacobian_truth"],
            )
        )
        self.assertFalse(
            np.array_equal(
                baseline.samples["tau_residual_raw"],
                different_noise.samples["tau_residual_raw"],
            )
        )

    def test_operational_flag_is_separate_from_numerical_rank(self):
        specification = h2_pairing_specification()
        specification["replicates"] = 1
        specification["factors"]["characteristic_length_m"] = [0.3]
        specification["factors"]["damping"] = [0.03]
        specification["factors"]["torque_noise_std_Nm"] = [0.01]
        specification["factors"]["operational_min_scaled_singular"] = [10.0]
        trial = build_trial_plan(specification)["trials"][0]
        payload = execute_h2_synthetic(trial)

        np.testing.assert_array_equal(
            payload.samples["solver_numerical_rank_deficient"],
            np.zeros(12, dtype=np.int8),
        )
        np.testing.assert_array_equal(
            payload.samples["solver_operationally_ill_conditioned"],
            np.ones(12, dtype=np.int8),
        )
        np.testing.assert_array_equal(
            payload.samples["operational_min_scaled_singular_threshold"],
            np.full(12, 10.0),
        )
        definition = payload.metadata[
            "operational_ill_conditioning_definition"
        ]
        self.assertTrue(definition["numerical_rank_is_reported_separately"])

    def test_v2_scan_has_sixteen_physical_groups_and_576_trials(self):
        specification = load_document(
            CODE_ROOT / "config" / "experiments" / "h2_calibration_v2.json"
        )
        plan = build_trial_plan(specification)
        self.assertEqual(plan["pair_count"], 144)
        self.assertEqual(plan["trial_count"], 576)

        group_ids = set()
        for trial in plan["trials"]:
            payload = execute_h2_synthetic(
                {
                    **trial,
                    "trajectory": {
                        **trial["trajectory"],
                        "sample_count": 8,
                    },
                }
            )
            group_ids.add(payload.metadata["h2_data_group_id"])
            if len(group_ids) == 16:
                break
        self.assertEqual(len(group_ids), 16)


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