"""Tests for the E₈ BCC Cube — Exterior Algebra Theorem (Result 26).

The 3×3×3 sign-based projection of 240 E₈ roots has orbit multiplicities
{16, 1, 10, 40} encoding:
  40 - 10 = 30 = h(E₈)           (Coxeter number)
  16 + 10 + 1 = 27 = 3³          (Triality)
  40 + 16 = 56 = C(8,3)          (dim ∧³R⁸)
  28 projections = C(8,2)         (dim ∧²R⁸)
  corners = 16 = L(3)² = 2⁴     (half-spin)
"""

import numpy as np
from itertools import combinations, product
from math import comb

from watkins_nn.kaleidoscope import lucas, mersenne


def _generate_e8_roots():
    """Generate all 240 E₈ roots."""
    roots = []
    for i, j in combinations(range(8), 2):
        for si in [1, -1]:
            for sj in [1, -1]:
                r = [0]*8
                r[i] = si
                r[j] = sj
                roots.append(r)
    for signs in product([1, -1], repeat=8):
        if sum(1 for s in signs if s == -1) % 2 == 0:
            roots.append([s * 0.5 for s in signs])
    return np.array(roots)


def _project_to_grid_2d(roots, i, j):
    """Project E₈ roots to 3×3 grid via coordinate pair (i,j)."""
    grid = np.zeros((3, 3), dtype=int)
    for r in roots:
        ri = 0 if r[i] < -0.1 else (2 if r[i] > 0.1 else 1)
        ci = 0 if r[j] < -0.1 else (2 if r[j] > 0.1 else 1)
        grid[ri, ci] += 1
    return grid


def _project_to_cube_3d(roots, i, j, k):
    """Project E₈ roots to 3×3×3 cube via coordinate triple (i,j,k)."""
    cube = np.zeros((3, 3, 3), dtype=int)
    for r in roots:
        x = 0 if r[i] < -0.1 else (2 if r[i] > 0.1 else 1)
        y = 0 if r[j] < -0.1 else (2 if r[j] > 0.1 else 1)
        z = 0 if r[k] < -0.1 else (2 if r[k] > 0.1 else 1)
        cube[x, y, z] += 1
    return cube


ROOTS = _generate_e8_roots()
CANONICAL_GRID = np.array([[33, 12, 33], [12, 60, 12], [33, 12, 33]])


# ── E₈ root count ────────────────────────────────────────────────────────────

def test_240_roots():
    assert len(ROOTS) == 240
    norms_sq = np.sum(ROOTS**2, axis=1)
    assert np.allclose(norms_sq, 2.0)


# ── 3×3 grid universality ────────────────────────────────────────────────────

def test_canonical_grid():
    grid = _project_to_grid_2d(ROOTS, 0, 1)
    assert np.array_equal(grid, CANONICAL_GRID)


def test_all_28_projections_identical():
    for i, j in combinations(range(8), 2):
        grid = _project_to_grid_2d(ROOTS, i, j)
        assert np.array_equal(grid, CANONICAL_GRID), \
            f"Projection ({i},{j}) differs: {grid}"


def test_grid_null_vector():
    v = np.array([1, 0, -1])
    assert np.allclose(CANONICAL_GRID @ v, 0)


def test_grid_entries_lucas():
    assert CANONICAL_GRID[0, 0] == lucas(2) * lucas(5)  # 33 = 3·11
    assert CANONICAL_GRID[1, 1] == 60  # |A₅|


def test_grid_eigenvalues():
    eigs = sorted(np.linalg.eigvalsh(CANONICAL_GRID), reverse=True)
    m6 = mersenne(6)
    delta = 3 * np.sqrt(lucas(2) * lucas(5))
    assert abs(eigs[0] - (m6 + delta)) < 0.01
    assert abs(eigs[1] - (m6 - delta)) < 0.01
    assert abs(eigs[2]) < 1e-10


# ── 3×3×3 BCC cube ──────────────────────────────────────────────────────────

def test_bcc_cube_total():
    cube = _project_to_cube_3d(ROOTS, 0, 1, 2)
    assert cube.sum() == 240


def test_bcc_orbit_multiplicities():
    """BCC orbit types: corner=16, edge=1, face=10, center=40."""
    cube = _project_to_cube_3d(ROOTS, 0, 1, 2)
    for x, y, z in product(range(3), repeat=3):
        center_count = sum(1 for c in [x, y, z] if c == 1)
        if center_count == 0:     # corner
            assert cube[x, y, z] == 16
        elif center_count == 1:   # edge center
            assert cube[x, y, z] == 1
        elif center_count == 2:   # face center
            assert cube[x, y, z] == 10
        else:                     # body center
            assert cube[x, y, z] == 40


def test_all_56_triples_identical():
    ref = _project_to_cube_3d(ROOTS, 0, 1, 2)
    for i, j, k in combinations(range(8), 3):
        cube = _project_to_cube_3d(ROOTS, i, j, k)
        assert np.array_equal(cube, ref), \
            f"Triple ({i},{j},{k}) differs"


# ── Exterior algebra relationships ───────────────────────────────────────────

def test_coxeter_number():
    """40 - 10 = 30 = h(E₈)."""
    assert 40 - 10 == 30


def test_triality_count():
    """16 + 10 + 1 = 27 = 3³ (Triality)."""
    assert 16 + 10 + 1 == 27


def test_exterior_cube():
    """40 + 16 = 56 = C(8,3) = dim(∧³R⁸)."""
    assert 40 + 16 == comb(8, 3)


def test_projection_count():
    """28 = C(8,2) = number of coordinate-pair projections."""
    assert comb(8, 2) == 28


def test_corners_lucas_square():
    """16 = L(3)² = 4²."""
    assert 16 == lucas(3)**2


def test_half_spin_decomposition():
    """Corners × 8 = 128 = half the 256 half-integer sign patterns (even chirality)."""
    assert 16 * 8 == 128
