"""Tests for watkins_nn.triality module (27-term Grand Unifier)."""

import pytest
import math


# =============================================================================
# ENUMERATION TESTS
# =============================================================================

class TestEnumerations:

    def test_domain_values(self):
        from watkins_nn.triality import Domain
        assert int(Domain.QUALIA) == 1
        assert int(Domain.BAO) == 2
        assert int(Domain.MERA_QG) == 3

    def test_harmonic_values(self):
        from watkins_nn.triality import Harmonic
        assert int(Harmonic.FAST) == 1
        assert int(Harmonic.SLOW) == 2
        assert int(Harmonic.WHITE_HOLE) == 3

    def test_scale_values(self):
        from watkins_nn.triality import Scale
        assert int(Scale.MICRO) == 1
        assert int(Scale.MESO) == 2
        assert int(Scale.MACRO) == 3


# =============================================================================
# OMEGA MATRIX TESTS
# =============================================================================

class TestOmegaMatrix:

    def test_omega_matrix_size(self):
        from watkins_nn.triality import OMEGA_MATRIX
        assert len(OMEGA_MATRIX) == 27

    def test_omega_identity(self):
        from watkins_nn.triality import OMEGA_MATRIX
        assert abs(OMEGA_MATRIX[(1, 1, 1)] - 1.0) < 1e-15

    def test_omega_center(self):
        from watkins_nn.triality import OMEGA_MATRIX
        from watkins_nn.constants import BETA_STAR
        assert abs(OMEGA_MATRIX[(2, 2, 2)] - BETA_STAR) < 1e-10

    def test_omega_max(self):
        from watkins_nn.triality import OMEGA_MAX
        from watkins_nn.constants import PHI, W_SQUARED
        expected = PHI ** (4 * W_SQUARED)
        assert abs(OMEGA_MAX - expected) < 1e-10

    def test_omega_formula(self):
        from watkins_nn.triality import OMEGA_MATRIX
        from watkins_nn.constants import PHI, W_SQUARED
        for d in range(1, 4):
            for h in range(1, 4):
                for s in range(1, 4):
                    exponent = (d + h + s - 3) / 3
                    expected = PHI ** (2 * W_SQUARED * exponent)
                    actual = OMEGA_MATRIX[(d, h, s)]
                    assert abs(actual - expected) < 1e-12, f"Mismatch at ({d},{h},{s})"

    def test_omega_mean(self):
        from watkins_nn.triality import OMEGA_MATRIX, OMEGA_MEAN
        computed_mean = sum(OMEGA_MATRIX.values()) / 27
        assert abs(computed_mean - OMEGA_MEAN) < 1e-10

    def test_omega_ordering(self):
        from watkins_nn.triality import OMEGA_MIN, OMEGA_CENTER, OMEGA_MAX
        assert OMEGA_MIN < OMEGA_CENTER < OMEGA_MAX


# =============================================================================
# TRIALITY INDEX TESTS
# =============================================================================

class TestTrialityIndex:

    def test_create_index(self):
        from watkins_nn.triality import TrialityIndex, Domain, Harmonic, Scale
        idx = TrialityIndex(Domain.QUALIA, Harmonic.SLOW, Scale.MESO)
        assert idx.d == 1
        assert idx.h == 2
        assert idx.s == 2

    def test_omega_property(self):
        from watkins_nn.triality import TrialityIndex, Domain, Harmonic, Scale, OMEGA_MATRIX
        idx = TrialityIndex(Domain.QUALIA, Harmonic.SLOW, Scale.MESO)
        expected = OMEGA_MATRIX[(1, 2, 2)]
        assert abs(idx.omega - expected) < 1e-15

    def test_linear_index_first(self):
        from watkins_nn.triality import TrialityIndex, Domain, Harmonic, Scale
        idx = TrialityIndex(Domain.QUALIA, Harmonic.FAST, Scale.MICRO)
        assert idx.linear_index == 1

    def test_linear_index_last(self):
        from watkins_nn.triality import TrialityIndex, Domain, Harmonic, Scale
        idx = TrialityIndex(Domain.MERA_QG, Harmonic.WHITE_HOLE, Scale.MACRO)
        assert idx.linear_index == 27

    def test_from_linear_roundtrip(self):
        from watkins_nn.triality import TrialityIndex
        for linear in range(1, 28):
            idx = TrialityIndex.from_linear(linear)
            assert idx.linear_index == linear

    def test_all_indices(self):
        from watkins_nn.triality import TrialityIndex
        all_idx = TrialityIndex.all_indices()
        assert len(all_idx) == 27
        linear_indices = [idx.linear_index for idx in all_idx]
        assert len(set(linear_indices)) == 27

    def test_invalid_linear_index(self):
        from watkins_nn.triality import TrialityIndex
        with pytest.raises(ValueError):
            TrialityIndex.from_linear(0)
        with pytest.raises(ValueError):
            TrialityIndex.from_linear(28)


# =============================================================================
# TRIALITY ENGINE TESTS
# =============================================================================

class TestTrialityEngine:

    def test_core_threshold(self):
        from watkins_nn.triality import TrialityEngine
        from watkins_nn.qwarp import p_half_qwarp
        from watkins_nn.constants import LAM_STAR
        engine = TrialityEngine()
        core = engine.core_threshold(64)
        expected = p_half_qwarp(64, 1.0, 0.05, LAM_STAR, terms=12)
        assert abs(core - expected) < 1e-15

    def test_threshold_with_omega(self):
        from watkins_nn.triality import TrialityEngine, Domain, Harmonic, Scale
        engine = TrialityEngine()
        n = 256
        core = engine.core_threshold(n)
        omega = engine.omega(Domain.QUALIA, Harmonic.SLOW, Scale.MESO)
        threshold = engine.threshold(n, Domain.QUALIA, Harmonic.SLOW, Scale.MESO)
        assert abs(threshold - core * omega) < 1e-15

    def test_unified_threshold(self):
        from watkins_nn.triality import TrialityEngine, OMEGA_MEAN
        engine = TrialityEngine()
        unified = engine.unified_threshold(128)
        expected = engine.core_threshold(128) * OMEGA_MEAN
        assert abs(unified - expected) < 1e-15

    def test_triality_table_size(self):
        from watkins_nn.triality import TrialityEngine
        engine = TrialityEngine()
        table = engine.triality_table(64)
        assert len(table) == 27

    def test_triality_matrix_keys(self):
        from watkins_nn.triality import TrialityEngine
        engine = TrialityEngine()
        matrix = engine.triality_matrix(64)
        assert len(matrix) == 27
        for d in range(1, 4):
            for h in range(1, 4):
                for s in range(1, 4):
                    assert (d, h, s) in matrix


# =============================================================================
# TRIALITY RUNNER TESTS
# =============================================================================

class TestTrialityRunner:

    def test_run_returns_result(self):
        from watkins_nn.triality import TrialityRunner
        from watkins_nn.constants import LAM_STAR
        runner = TrialityRunner()
        result = runner.run(256)
        assert result.n == 256
        assert result.p_12_core > 0
        assert result.p_unified > 0
        assert abs(result.concurrence - 0.9717) < 0.001
        assert abs(result.lambda_star - LAM_STAR) < 1e-10

    def test_run_table(self):
        from watkins_nn.triality import TrialityRunner
        runner = TrialityRunner()
        results = runner.run_table([64, 128, 256])
        assert len(results) == 3
        assert results[0].p_12_core > results[1].p_12_core > results[2].p_12_core

    def test_run_mera(self):
        from watkins_nn.triality import TrialityRunner
        runner = TrialityRunner()
        layers = runner.run_mera(total_layers=8)
        assert len(layers) == 8
        layer_nums = [l[0] for l in layers]
        assert layer_nums == list(range(1, 9))


# =============================================================================
# CONVENIENCE FUNCTION TESTS
# =============================================================================

class TestConvenienceFunctions:

    def test_omega_27(self):
        from watkins_nn.triality import omega_27, OMEGA_MATRIX
        for d in range(1, 4):
            for h in range(1, 4):
                for s in range(1, 4):
                    assert omega_27(d, h, s) == OMEGA_MATRIX[(d, h, s)]

    def test_omega_27_invalid(self):
        from watkins_nn.triality import omega_27
        with pytest.raises(ValueError):
            omega_27(0, 1, 1)
        with pytest.raises(ValueError):
            omega_27(1, 4, 1)

    def test_p_27_threshold(self):
        from watkins_nn.triality import p_27_threshold, OMEGA_MATRIX
        from watkins_nn.qwarp import p_half_qwarp
        from watkins_nn.constants import LAM_STAR
        n = 128
        d, h, s = 2, 2, 2
        p = p_27_threshold(n, d, h, s)
        core = p_half_qwarp(n, 1.0, 0.05, LAM_STAR, terms=12)
        omega = OMEGA_MATRIX[(d, h, s)]
        assert abs(p - core * omega) < 1e-15

    def test_triality_unified(self):
        from watkins_nn.triality import triality_unified, OMEGA_MEAN
        from watkins_nn.qwarp import p_half_qwarp
        from watkins_nn.constants import LAM_STAR
        n = 64
        unified = triality_unified(n)
        core = p_half_qwarp(n, 1.0, 0.05, LAM_STAR, terms=12)
        assert abs(unified - core * OMEGA_MEAN) < 1e-15


# =============================================================================
# NUMERICAL VALIDATION TESTS
# =============================================================================

class TestNumericalValidation:

    def test_mu_fast(self):
        from watkins_nn.triality import MU_FAST_4BODY
        assert abs(MU_FAST_4BODY - 18.347) < 0.01

    def test_mu_slow(self):
        from watkins_nn.triality import MU_SLOW_4BODY
        assert abs(MU_SLOW_4BODY - 4.872) < 0.01

    def test_tau_fast(self):
        from watkins_nn.triality import MU_FAST_4BODY
        tau = 1 / MU_FAST_4BODY
        assert abs(tau - 0.055) < 0.001

    def test_tau_slow(self):
        from watkins_nn.triality import MU_SLOW_4BODY
        tau = 1 / MU_SLOW_4BODY
        assert abs(tau - 0.205) < 0.001

    def test_omega_center_is_beta_star(self):
        from watkins_nn.triality import OMEGA_CENTER
        assert abs(OMEGA_CENTER - 1.9506) < 0.001

    def test_omega_max_value(self):
        from watkins_nn.triality import OMEGA_MAX
        assert abs(OMEGA_MAX - 3.805) < 0.001


# =============================================================================
# INTEGRATION TESTS
# =============================================================================

class TestIntegration:

    def test_full_pipeline(self):
        from watkins_nn.triality import TrialityRunner
        from watkins_nn.constants import LAM_STAR
        runner = TrialityRunner()
        result = runner.run(256)
        assert abs(result.lambda_star - LAM_STAR) < 1e-10
        assert abs(result.concurrence - 0.9717) < 0.001
        assert result.p_qualia > 0
        assert result.p_bao > 0
        assert result.p_mera > 0
        assert result.p_unified > 0

    def test_cosmological_run(self):
        from watkins_nn.triality import TrialityRunner
        runner = TrialityRunner()
        bao_results = runner.run_cosmological([1090, 500, 100, 0])
        assert len(bao_results) == 4
        for z, p in bao_results:
            assert p > 0
