"""Independent character and normalization checks for numberdb.org/T609.

Run `sage -python checks.py` beside generate.py, or `sage -python checks.py
--stored` with NUMBERDB_API_KEY for the private draft. Campaign wrapper: set
NUMBERDB_KEY_FROM_STDIN=1 and pipe the key to agents/sage.sh. The latter mode
reads the authoritative document and checks its exact decimal intervals.

This checker evaluates Schur characters by Jacobi-Trudi determinants, with
complete homogeneous functions computed by coefficient convolution. It does
not use the generator's positive-root product for expected values. Permutation
expansion avoids Sage's lazily initialized matrix determinant machinery.
"""
import json
import math
import os
import sys
from collections import Counter, defaultdict
from decimal import Decimal
from fractions import Fraction
from functools import lru_cache
from itertools import permutations, product
from pathlib import Path

import numberdb.sage as numberdb
from sage.rings.rational_field import QQ
from sage.rings.number_field.number_field import CyclotomicField
from sage.rings.real_arb import RealBallField
from sage.rings.complex_arb import ComplexBallField
from sage.rings.real_mpfi import RealIntervalField
from numberdb._write import to_text
from numberdb._compare import digits_of
import sage.version
from generate import QuantumDimensions, exact_dimension, parse, authenticate_stdin

MANIFEST = 'agents/runs/20261029T130110Z-build-skills/manifest.json'
SKILL_SHA = '28ecbeeccb0c5e5d6eb541b5a5b0f88aec47b154374d909085116d34f5629199'
C = ComplexBallField(600)
R = RealBallField(600)
I = RealIntervalField(600)


def determinant(rows):
    n = len(rows)
    answer = rows[0][0].parent().zero()
    for perm in permutations(range(n)):
        term = rows[0][0].parent().one()
        inversions = sum(perm[i] > perm[j] for i in range(n) for j in range(i+1, n))
        for i, j in enumerate(perm):
            term *= rows[i][j]
        answer += (-1)**inversions * term
    return answer


def schur_character(a, xs):
    """Jacobi-Trudi, independent of the quantum Weyl product."""
    lam = [sum(a[i:]) for i in range(len(a))]
    ring = xs[0].parent()
    degree = lam[0] + len(lam) - 1
    h = [ring.one()] + [ring.zero()] * degree
    for x in xs:
        # Multiply by (1-x*t)^(-1) without power-series machinery.
        old = h
        h = [sum(old[d-j] * x**j for j in range(d+1)) for d in range(degree+1)]
    def complete(d):
        return ring.zero() if d < 0 else h[d]
    return determinant([[complete(lam[i]-i+j) for j in range(len(lam))]
                        for i in range(len(lam))])


@lru_cache(None)
def expected_character(N, k, a):
    K = CyclotomicField(2 * (N+k))
    z = K.gen()
    return schur_character(a, [z**(N-1-2*j) for j in range(N)])


def embedding(exact, m):
    z = (C.pi() * C.gen(0) / m).exp()
    answer = C.zero()
    for c in reversed(exact.list()):
        answer = answer * z + C(c)
    assert answer.real().is_finite() and answer.imag().is_finite() and answer.imag().contains_zero()
    real = answer.real()
    assert real > 0 and R(real.rad()) < R(10)**(-150) * abs(R(real.center()))
    return real


def qdecimal(text):
    f = Fraction(Decimal(text))
    return QQ(f.numerator) / QQ(f.denominator)


def written_bounds(text):
    if '.' not in text and 'e' not in text.lower():
        value = QQ(text)
        return value, value
    d = Decimal(text)
    center = qdecimal(text)
    unit = QQ(10)**int(d.as_tuple().exponent)
    return center-unit, center+unit


def contains_enclosure(text, ball, exact):
    lo, hi = written_bounds(text)
    if lo == hi:
        return exact == lo
    return bool(ball > lo and ball < hi)


def global_dimension(N, k):
    denominator = R.one()
    for j in range(1, N):
        denominator *= (2 * (R.pi()*j/(N+k)).sin())**(2*(N-j))
    return R(N) * (N+k)**(N-1) / denominator


def bounded_agreement(left, right):
    left, right = R(left), R(right)
    for b in (left, right):
        assert b.is_finite() and b > 0
        assert R(b.rad()) < abs(R(b.center())) * R(10)**(-94)
    assert left.overlaps(right)


def main():
    authenticate_stdin()
    use_stored = '--stored' in sys.argv or os.environ.get('NUMBERDB_RESTATING') == '1'
    # Known-answer and deliberate-error controls precede every measurement.
    assert determinant([[QQ(1), QQ(2)], [QQ(3), QQ(4)]]) == -2
    assert schur_character((1,1), [QQ(1)]*3) == 8
    assert expected_character(2,1,(0,)) == 1
    sqrt_two = expected_character(2,2,(1,))
    assert sqrt_two**2 == 2
    phi = expected_character(2,3,(1,))
    assert phi**2 - phi - 1 == 0
    assert expected_character(2,4,(2,)) == 2
    assert expected_character(3,3,(1,1)) == 3
    phi_ball = embedding(phi,5)
    assert phi_ball > QQ(3)/2 and phi_ball < QQ(5)/3
    text = to_text(phi_ball, digits=100)
    assert contains_enclosure(text, phi_ball, phi)
    assert not contains_enclosure('1.700', phi_ball, phi)
    assert not contains_enclosure('2', phi_ball, phi)
    g = QuantumDimensions()
    params = list(g.enumerate())
    # Independent rectangular enumeration is used only to audit the alcove.
    independently_selected = []
    for N,max_k in ((2,20),(3,8),(4,5),(5,4)):
        for k in range(1,max_k+1):
            alcove = [a for a in product(range(k+1), repeat=N-1) if sum(a)<=k]
            assert len(alcove) == math.comb(k+N-1,N-1)
            independently_selected.extend((N,k,a) for a in sorted(alcove,key=lambda a:(sum(a),a)))
    assert [parse(p) for p in params] == independently_selected
    if use_stored:
        document = numberdb.table(g.table)
        records = document['Numbers']
        assert isinstance(records,list), 'Expected flat records from this generator'
        assert [parse(r['params']) for r in records] == independently_selected
    else:
        records = []
        for p in params:
            rec = g.value(p,100)
            records.append({'params':p,**rec,'number':to_text(rec['number'],digits=100)})
    exact_values = {}
    numeric = {}
    rational_count = 0
    min_accuracy = 1000000
    min_embedded_accuracy = 1000000
    max_relative_radius = R.zero()
    for record in records:
        key = N,k,a = parse(record['params'])
        expected = expected_character(N,k,a)
        # Exact equality between character and root-product algorithms.
        actual_exact = exact_dimension(N,k,a)
        assert expected == actual_exact, key
        exact_values[key] = expected
        emb = embedding(expected,N+k)
        if emb.rad() != 0:
            min_embedded_accuracy = min(min_embedded_accuracy,int(emb.accuracy()))
        text = str(record['number'])
        assert contains_enclosure(text,emb,expected), (key,text)
        lo,hi = written_bounds(text)
        numeric[key] = R(I(lo,hi))
        if lo == hi:
            rational_count += 1
        else:
            assert digits_of(text) >= 100, (key,text,digits_of(text))
        value = g.value(record['params'],100)['number']
        if callable(getattr(value,'rad',None)):
            assert value.is_finite()
            min_accuracy = min(min_accuracy,int(value.accuracy()))
            relative = R(value.rad()) / abs(R(value.center()))
            if relative > max_relative_radius:
                max_relative_radius = relative
        if len(exact_values) % 100 == 0:
            print('checked characters and serialized enclosures',len(exact_values),flush=True)
    # Conjugation and the affine Dynkin rotation (simple-current action).
    for (N,k,a),value in exact_values.items():
        conjugate=(N,k,tuple(reversed(a)))
        rotated=(N,k,(k-sum(a),)+a[:-1])
        assert value == exact_values[conjugate] == exact_values[rotated]
        bounded_agreement(numeric[N,k,a],numeric[conjugate])
        bounded_agreement(numeric[N,k,a],numeric[rotated])
    # Character evaluation checks the Schur specialization for every entry.
    # SU(2) Chebyshev recurrence, fundamental cosines, and the full fusion rule.
    fusion_checks = 0
    for k in range(1,21):
        x=R.pi()/(k+2)
        fundamental=2*x.cos()
        u0,u1=R.one(),fundamental
        bounded_agreement(numeric[2,k,(0,)],u0)
        bounded_agreement(numeric[2,k,(1,)],u1)
        for a in range(2,k+1):
            u0,u1=u1,fundamental*u1-u0
            bounded_agreement(numeric[2,k,(a,)],u1)
        for a in range(k+1):
            for b in range(k+1):
                indices=range(abs(a-b),min(a+b,2*k-a-b)+1,2)
                assert exact_values[2,k,(a,)]*exact_values[2,k,(b,)] == sum(exact_values[2,k,(c,)] for c in indices)
                bounded_agreement(numeric[2,k,(a,)]*numeric[2,k,(b,)],sum(numeric[2,k,(c,)] for c in indices))
                fusion_checks += 1
    # Independent category normalization includes every vacuum and dimension-one row.
    sums=defaultdict(lambda:R.zero())
    for (N,k,a),value in numeric.items():
        sums[N,k]+=value**2
    for (N,k),total in sums.items():
        bounded_agreement(total,global_dimension(N,k))
    # Fundamental path adjacency eigenvector: positive sine vector, boundary zero.
    for k in range(1,21):
        t=R.pi()/(k+2)
        vector=[(j*t).sin() for j in range(1,k+2)]
        lam=numeric[2,k,(1,)]
        for i,v in enumerate(vector):
            adj=(vector[i-1] if i else R.zero())+(vector[i+1] if i<k else R.zero())
            bounded_agreement(adj,lam*v)
    by_rank=Counter(int(r['params']['N']) for r in records)
    import yaml
    evidence={
      'table':g.table,'mode':'database readback' if use_stored else 'offline',
      'skill_manifest':MANIFEST,'skill_local_sha256':SKILL_SHA,'skill_live_sha256':SKILL_SHA,
      'versions':{'sage':sage.version.version,'numberdb':numberdb.__version__},
      'entries':len(records),'alcoves':len(sums),'by_matrix_size':dict(by_rank),
      'exact_rational_entries':rational_count,'certified_decimal_entries':len(records)-rational_count,
      'digits':100,'working_bits':numberdb.bits(100,losing=96),
      'minimum_generator_accuracy_bits':min_accuracy,
      'minimum_independent_embedding_accuracy_bits':min_embedded_accuracy,
      'maximum_relative_radius':str(max_relative_radius),
      'longest_serialized_value_characters':max(len(str(r['number'])) for r in records),
      'serialized_flat_yaml_bytes':len(yaml.safe_dump(records,sort_keys=False).encode()),
      'checks':{'known_answer_and_negative_controls':True,'independent_ordered_enumeration':len(records),
                'exact_character_equalities':len(records),'independent_serialized_enclosures':len(records),
                'conjugation_equalities':len(records),'simple_current_equalities':len(records),
                'su2_chebyshev_rows':230,'su2_fusion_identities':fusion_checks,
                'global_dimension_normalizations':len(sums),'path_eigenvectors':20},
      'limitations':'Builder checks are not an independent critique; no review acceptance is asserted.'}
    print('EVIDENCE_JSON='+json.dumps(evidence,sort_keys=True),flush=True)
    print('VALUES_JSON='+json.dumps(records,sort_keys=False),flush=True)


if __name__ == '__main__':
    main()
