"""Tests for validation synthetic data generator."""

from __future__ import annotations

import random
import tempfile
from pathlib import Path

from orxaq.fabric.validation.synthetic import (
    GENERATION_ORDER,
    VARIABLE_SPECS,
    ValidationDataGenerator,
)


class TestValidationDataGenerator:
    def setup_method(self):
        self.gen = ValidationDataGenerator()

    def test_generate_correct_count(self):
        rows = self.gen.generate(n=100, seed=42)
        assert len(rows) == 100

    def test_all_variables_present(self):
        rows = self.gen.generate(n=10, seed=42)
        for row in rows:
            assert set(row.keys()) == set(GENERATION_ORDER)

    def test_values_within_bounds(self):
        rows = self.gen.generate(n=1000, seed=42)
        for row in rows:
            for var_name, value in row.items():
                spec = VARIABLE_SPECS[var_name]
                assert spec.min_val <= value <= spec.max_val, (
                    f"{var_name}={value} outside [{spec.min_val}, {spec.max_val}]"
                )

    def test_reproducible_with_same_seed(self):
        rows1 = self.gen.generate(n=50, seed=99)
        rows2 = self.gen.generate(n=50, seed=99)
        assert rows1 == rows2

    def test_different_seeds_differ(self):
        rows1 = self.gen.generate(n=50, seed=1)
        rows2 = self.gen.generate(n=50, seed=2)
        assert rows1 != rows2

    def test_true_edges_not_empty(self):
        assert len(self.gen.true_edges) > 0

    def test_true_edges_are_tuples(self):
        for edge in self.gen.true_edges:
            assert isinstance(edge, tuple)
            assert len(edge) == 2

    def test_variables_list(self):
        assert len(self.gen.variables) == 20

    def test_save_csv(self):
        rows = self.gen.generate(n=10, seed=42)
        with tempfile.TemporaryDirectory() as tmp:
            path = Path(tmp) / "test.csv"
            self.gen.save_csv(rows, path)
            assert path.exists()
            lines = path.read_text().strip().split("\n")
            assert len(lines) == 11  # header + 10 rows

    def test_causal_structure_embedded(self):
        """Verify causal relationships are detectable in generated data."""
        rows = self.gen.generate(n=5000, seed=42)
        # Unemployment should correlate negatively with GDP growth.
        gdp = [r["gdp_growth"] for r in rows]
        unemp = [r["unemployment"] for r in rows]
        n = len(gdp)
        mean_g = sum(gdp) / n
        mean_u = sum(unemp) / n
        cov = sum((g - mean_g) * (u - mean_u) for g, u in zip(gdp, unemp)) / n
        # Should be negative (GDP growth reduces unemployment).
        assert cov < 0

    def test_large_dataset_generates_fast(self):
        """50K rows should complete without error."""
        rows = self.gen.generate(n=50000, seed=42)
        assert len(rows) == 50000


class TestGeneratorWithCustomDAG:
    def test_load_from_path(self):
        dag_path = (
            Path(__file__).parent.parent.parent / "data" / "validation" / "ground_truth_dag.json"
        )
        gen = ValidationDataGenerator(dag_path)
        assert len(gen.true_edges) == 25


# ── Targeted coverage tests ──────────────────────────────────


class TestDefaultDagFallback:
    """Cover line 106 (_default_dag when no dag file exists)."""

    def test_no_dag_file_uses_empty_edges(self):
        """When ground_truth_dag.json doesn't exist, use empty edges."""
        from unittest.mock import patch

        with patch("pathlib.Path.exists", return_value=False):
            gen = ValidationDataGenerator(dag_path=None)
        assert gen.true_edges == []
        rows = gen.generate(n=10, seed=42)
        assert len(rows) == 10


class TestGaussMethod:
    """Cover lines 161-164 (_gauss Box-Muller method)."""

    def test_gauss_produces_normal_values(self):
        """_gauss should produce approximately normal-distributed values."""
        gen = ValidationDataGenerator()
        rng = random.Random(42)
        values = [gen._gauss(rng, 0.0, 1.0) for _ in range(1000)]
        mean = sum(values) / len(values)
        assert abs(mean) < 0.2  # approximately 0
        var = sum((v - mean) ** 2 for v in values) / len(values)
        assert 0.5 < var < 2.0  # approximately 1
