"""Sprint 15: Hyperbolic Volumes and the p-Spectrum (Theorems BY-CC).

Extracted from bridges.py for module restructure (v2.0).
"""

import cmath
import math
from typing import Dict

import numpy as np

from .constants import PHI, PHI_INV, T_STAR, LAM_STAR, KAP_STAR, ETA_STAR, F_STAR, MU_SLOW, MU_FAST
from .bridges import root_system_conservation, _free_energy
from .cascade import an_bcc_eigenvalues, bn_bcc_formula, cn_bcc_formula, dn_bcc_formula

# =============================================================================
# SPRINT 15: Hyperbolic Volumes and the p-Spectrum (BY-CC)
# =============================================================================

def _bloch_wigner(z: complex) -> float:
    """Bloch-Wigner function D(z) = Im(Li_2(z)) + arg(1-z) * ln|z|.

    Computes the dilogarithm via power series (converges for |z| < 1).
    """
    if abs(z) < 1e-15 or abs(z - 1) < 1e-15:
        return 0.0
    li2 = 0.0 + 0j
    zn = z
    for k in range(1, 500):
        li2 += zn / (k * k)
        zn *= z
        if abs(zn) < 1e-30:
            break
    return li2.imag + cmath.phase(1 - z) * math.log(abs(z))


def root_system_complex_invariant() -> Dict:
    """Map exceptional root systems to complex invariants z_R = lam_R + i*kap_R.

    Theorem BY (Root System Complex Invariant):
    Each exceptional root system's conservation triple lifts to a complex
    number z_R = lambda_R + i*kappa_R in the upper half-plane.  This
    defines a ROOT SYSTEM CONSTELLATION with rich structure:

    1. Modulus |z_R| DECREASES through the cascade: F4 > E6 > E7 > E8,
       with the equilibrium |z_eq| ≈ 0.647 being the smallest.
    2. Argument arg(z_R) = atan(gamma_R) INCREASES through the cascade,
       where gamma_R = kappa_R/lambda_R is the coupling from Theorem BW.
    3. Complex distance |z_R - z_eq| to equilibrium is minimized by E7
       (NOT E8!) — a different system wins in a different metric.

    This reveals multi-optimality: E8 is closest in lambda (coherence),
    E6 in gamma (coupling), and E7 in complex distance.  No single
    system is universally closest to the golden equilibrium.

    Returns:
        Dict with complex invariant analysis for each system.
    """
    z_eq = complex(LAM_STAR, KAP_STAR)
    mod_eq = abs(z_eq)
    arg_eq = cmath.phase(z_eq)

    results = []
    for name in ['F4', 'E6', 'E7', 'E8']:
        r = root_system_conservation(name)
        z = complex(r['lambda_R'], max(r['kappa_R'], 0))
        mod = abs(z)
        arg_val = cmath.phase(z)
        dist = abs(z - z_eq)

        results.append({
            'system': name,
            'z_real': z.real,
            'z_imag': z.imag,
            'modulus': mod,
            'argument': arg_val,
            'argument_degrees': math.degrees(arg_val),
            'complex_distance': dist,
        })

    # Ordering checks
    mods = [r['modulus'] for r in results]
    args = [r['argument'] for r in results]
    dists = [r['complex_distance'] for r in results]

    mod_decreasing = all(mods[i] > mods[i + 1] for i in range(len(mods) - 1))
    arg_increasing = all(args[i] < args[i + 1] for i in range(len(args) - 1))

    closest_complex = min(results, key=lambda r: r['complex_distance'])

    return {
        'systems': results,
        'z_equilibrium': {'real': z_eq.real, 'imag': z_eq.imag,
                          'modulus': mod_eq, 'argument': arg_eq},
        'modulus_decreasing': mod_decreasing,
        'argument_increasing': arg_increasing,
        'closest_by_complex_distance': closest_complex['system'],
        'closest_by_lambda': 'E8',   # Known from Sprint 14
        'closest_by_coupling': 'E6',  # Known from BW
        'multi_optimal': (closest_complex['system'] != 'E8'
                          and closest_complex['system'] != 'E6'),
    }


def bloch_wigner_volume_landscape() -> Dict:
    """Compute Bloch-Wigner volumes for root system complex invariants.

    Theorem BZ (Bloch-Wigner Volume Landscape):
    The Bloch-Wigner function D(z) assigns a hyperbolic volume to each
    root system's complex invariant z_R.  The volume ratios D(z_R)/D(z_eq)
    reveal algebraic structure invisible on the real simplex:

        F4:  D/D_eq = 0.6676  ≈  2/3    (0.13% error)
        E6:  D/D_eq = 1.0763
        E7:  D/D_eq = 1.4137  ≈  sqrt(2) (0.034% error)  ← !!
        E8:  D/D_eq = 1.5880

    The E7 ratio matching sqrt(2) to 0.034% is the strongest candidate
    for an exact identity.  If D(z_E7)/D(z_eq) = sqrt(2), it connects
    the Bloch-Wigner dilogarithm (hyperbolic geometry) to the E7 root
    system through an algebraic constant INDEPENDENT of phi.

    The volume ordering matches the coupling ordering from BW:
        D(F4) < D(E6) < D(E7) < D(E8)
    establishing volume-coupling monotonicity.

    Returns:
        Dict with Bloch-Wigner volumes and ratio analysis.
    """
    z_eq = complex(LAM_STAR, KAP_STAR)
    D_eq = _bloch_wigner(z_eq)

    results = []
    for name in ['F4', 'E6', 'E7', 'E8']:
        r = root_system_conservation(name)
        z = complex(r['lambda_R'], max(r['kappa_R'], 0))
        D_val = _bloch_wigner(z)
        ratio = D_val / D_eq if D_eq != 0 else 0

        results.append({
            'system': name,
            'D_volume': D_val,
            'D_ratio': ratio,
        })

    # Named constant checks
    e7 = next(r for r in results if r['system'] == 'E7')
    f4 = next(r for r in results if r['system'] == 'F4')
    sqrt2 = math.sqrt(2)

    e7_sqrt2_error = abs(e7['D_ratio'] - sqrt2) / sqrt2
    f4_two_thirds_error = abs(f4['D_ratio'] - 2 / 3) / (2 / 3)

    # Volume ordering matches coupling ordering
    ratios = [r['D_ratio'] for r in results]
    volume_monotone = all(ratios[i] < ratios[i + 1]
                          for i in range(len(ratios) - 1))

    return {
        'systems': results,
        'D_equilibrium': D_eq,
        'e7_ratio': e7['D_ratio'],
        'e7_sqrt2_error': e7_sqrt2_error,
        'e7_near_sqrt2': e7_sqrt2_error < 0.001,
        'f4_ratio': f4['D_ratio'],
        'f4_two_thirds_error': f4_two_thirds_error,
        'f4_near_two_thirds': f4_two_thirds_error < 0.005,
        'volume_coupling_monotone': volume_monotone,
    }


def negative_budget_scaling(n_max: int = 100) -> Dict:
    """Compute the simplex deficit scaling for classical families.

    Theorem CA (Negative Budget Scaling):
    For off-simplex root systems with lambda_R > 1, define the simplex
    deficit p_R = lambda_R - 1 (equivalently, p_R = |kappa_R| + |eta_R|
    when both are negative).

    The deficit scales as p_R * n^2 -> c_family as n -> infinity:

        A_n:  c_A = 2   (from Theorem AY: lambda = 1 + 2/n^2 + O(1/n^4))
        B_n:  c_B = 1
        C_n:  c_C = 1   (= c_B by Langlands duality)
        D_n:  c_D = 1

    The A_n coefficient (c=2) is DOUBLE the B/C/D coefficient (c=1).
    This ratio c_A / c_{B,C,D} = 2 connects to the cross-family Perron
    ratio from Theorem BH.

    B_n = C_n exactly (Langlands dual BCC grids) for all n.

    Returns:
        Dict with scaling data for each family.
    """
    families = {}

    # A_n scaling
    a_data = []
    for n in [10, 20, 50, 100, n_max]:
        if n < 3:
            continue
        eigs = an_bcc_eigenvalues(n)
        tr = sum(eigs)
        lam = eigs[0] / tr
        p_val = lam - 1
        a_data.append({'n': n, 'p': p_val, 'p_n2': p_val * n ** 2})
    families['A'] = a_data

    # B_n, C_n, D_n via BCC formulas
    for family, formula in [('B', 'bn'), ('C', 'cn'), ('D', 'dn')]:
        data = []
        for n in [10, 20, 50, 100]:
            if n < 3:
                continue
            if family == 'B':
                grid = bn_bcc_formula(n)
            elif family == 'C':
                grid = cn_bcc_formula(n)
            else:
                grid = dn_bcc_formula(n)
            eigs = sorted(np.linalg.eigvals(grid.astype(float)).real, reverse=True)
            tr = sum(eigs)
            if abs(tr) < 1e-12:
                continue
            lam = eigs[0] / tr
            p_val = lam - 1
            data.append({'n': n, 'p': p_val, 'p_n2': p_val * n ** 2})
        families[family] = data

    # Extract terminal values
    c_A = families['A'][-1]['p_n2'] if families['A'] else 0
    c_B = families['B'][-1]['p_n2'] if families['B'] else 0
    c_C = families['C'][-1]['p_n2'] if families['C'] else 0
    c_D = families['D'][-1]['p_n2'] if families['D'] else 0

    return {
        'families': families,
        'c_A': c_A,
        'c_B': c_B,
        'c_C': c_C,
        'c_D': c_D,
        'A_approaches_2': abs(c_A - 2) < 0.15,
        'B_approaches_1': abs(c_B - 1) < 0.15,
        'langlands_B_equals_C': all(
            abs(families['B'][i]['p_n2'] - families['C'][i]['p_n2']) < 1e-10
            for i in range(min(len(families['B']), len(families['C'])))
        ),
        'A_double_BCD': c_A / c_B > 1.5 if c_B > 0 else False,
    }


def volume_coupling_bridge() -> Dict:
    """Show volume-coupling monotonicity bridges Sprint 14 and Sprint 15.

    Theorem CB (Volume-Coupling Bridge):
    Four independent monotone quantities thread the exceptional cascade
    F4 -> E6 -> E7 -> E8, connecting the real simplex (Sprint 14) to
    the complex plane (Sprint 15):

        Decreasing:  lambda_R  (coherence)
                     kappa(H)  (Hessian condition number, Thm BT)
                     |z_R|     (complex modulus, Thm BY)

        Increasing:  gamma_R   (coupling, Thm BW)
                     D(z_R)    (Bloch-Wigner volume, Thm BZ)

    The volume-coupling correspondence D(z_R) ~ gamma_R reveals that
    the Bloch-Wigner dilogarithm (hyperbolic 3-manifold geometry)
    'sees' the same structure as the simplex curvature coupling.

    Returns:
        Dict with all four monotone quantities for each system.
    """
    z_eq = complex(LAM_STAR, KAP_STAR)
    D_eq = _bloch_wigner(z_eq)

    results = []
    for name in ['F4', 'E6', 'E7', 'E8']:
        r = root_system_conservation(name)
        lam = r['lambda_R']
        kap = r['kappa_R']
        gamma = kap / lam if lam > 0 else 0
        z = complex(lam, max(kap, 0))

        # Hessian condition number at symmetric projection (from BT)
        T = T_STAR
        kap_sym = (1 - lam) / 2
        H11 = 1 / lam ** 2 + T / lam + T / kap_sym
        H22 = T / kap_sym + T / kap_sym
        H12 = T / kap_sym
        tr = H11 + H22
        det = H11 * H22 - H12 ** 2
        disc = math.sqrt(max(tr ** 2 - 4 * det, 0))
        cond = ((tr + disc) / 2) / ((tr - disc) / 2) if (tr - disc) > 0 else float('inf')

        results.append({
            'system': name,
            'lambda_R': lam,
            'gamma': gamma,
            'condition_number': cond,
            'modulus': abs(z),
            'D_ratio': _bloch_wigner(z) / D_eq if D_eq > 0 else 0,
        })

    # Check all five monotone properties
    lams = [r['lambda_R'] for r in results]
    gammas = [r['gamma'] for r in results]
    conds = [r['condition_number'] for r in results]
    mods = [r['modulus'] for r in results]
    vols = [r['D_ratio'] for r in results]

    return {
        'systems': results,
        'lambda_decreasing': all(lams[i] > lams[i + 1] for i in range(len(lams) - 1)),
        'gamma_increasing': all(gammas[i] < gammas[i + 1] for i in range(len(gammas) - 1)),
        'condition_decreasing': all(conds[i] > conds[i + 1] for i in range(len(conds) - 1)),
        'modulus_decreasing': all(mods[i] > mods[i + 1] for i in range(len(mods) - 1)),
        'volume_increasing': all(vols[i] < vols[i + 1] for i in range(len(vols) - 1)),
        'all_five_monotone': (
            all(lams[i] > lams[i + 1] for i in range(len(lams) - 1))
            and all(gammas[i] < gammas[i + 1] for i in range(len(gammas) - 1))
            and all(conds[i] > conds[i + 1] for i in range(len(conds) - 1))
            and all(mods[i] > mods[i + 1] for i in range(len(mods) - 1))
            and all(vols[i] < vols[i + 1] for i in range(len(vols) - 1))
        ),
    }


def root_system_quantum_channel() -> Dict:
    """Formalize root system -> qutrit quantum channel mapping.

    Theorem CC (Root System Quantum Channel):
    Each on-simplex root system defines a qutrit replacement channel
    with target state rho_R = diag(lambda_R, kappa_R, eta_R).

    The quantum fidelity F(rho_R, rho_eq) = (sum sqrt(p_i * q_i))^2
    and von Neumann entropy S(rho_R) = -sum p_i * ln(p_i) reveal a
    MULTI-OPTIMALITY principle:

        E8:  highest S/S_eq = 0.705  (closest entropy to equilibrium)
        E6:  highest fidelity = 0.807  (best overlap with equilibrium)
        E7:  closest in complex distance (from BY)

    No single system is universally closest to the golden equilibrium.
    Each optimizes a DIFFERENT quantum information metric.

    The entropy ordering S(F4) < S(E6) < S(E7) < S(E8) is monotone
    through the cascade, while fidelity is NON-MONOTONE (E6 peaks).

    Returns:
        Dict with quantum channel metrics for each system.
    """
    S_eq = -(LAM_STAR * math.log(LAM_STAR)
             + KAP_STAR * math.log(KAP_STAR)
             + ETA_STAR * math.log(ETA_STAR))

    results = []
    for name in ['F4', 'E6', 'E7', 'E8']:
        r = root_system_conservation(name)
        lam = r['lambda_R']
        kap = r['kappa_R']
        eta = max(r['eta_R'], 0)

        # Bhattacharyya fidelity
        fid = (math.sqrt(lam * LAM_STAR)
               + math.sqrt(kap * KAP_STAR)
               + math.sqrt(eta * ETA_STAR)) ** 2

        # Von Neumann entropy
        S = 0.0
        for p in [lam, kap, eta]:
            if p > 1e-15:
                S -= p * math.log(p)
        S_ratio = S / S_eq if S_eq > 0 else 0

        results.append({
            'system': name,
            'fidelity': fid,
            'entropy': S,
            'entropy_ratio': S_ratio,
        })

    # Find optima
    best_fidelity = max(results, key=lambda r: r['fidelity'])
    best_entropy = max(results, key=lambda r: r['entropy_ratio'])

    # Entropy monotonicity
    entropies = [r['entropy'] for r in results]
    entropy_monotone = all(entropies[i] < entropies[i + 1]
                           for i in range(len(entropies) - 1))

    # Fidelity non-monotonicity
    fidelities = [r['fidelity'] for r in results]
    fidelity_monotone = all(fidelities[i] <= fidelities[i + 1]
                            for i in range(len(fidelities) - 1))

    return {
        'systems': results,
        'S_equilibrium': S_eq,
        'best_fidelity_system': best_fidelity['system'],
        'best_fidelity_value': best_fidelity['fidelity'],
        'best_entropy_system': best_entropy['system'],
        'best_entropy_ratio': best_entropy['entropy_ratio'],
        'entropy_monotone': entropy_monotone,
        'fidelity_non_monotone': not fidelity_monotone,
        'multi_optimal': best_fidelity['system'] != best_entropy['system'],
    }


