"""Independent checks for T653; run with sage -python checks.py.

No numerical approximation is used. Coordinate weights and a logarithmic-
derivative recurrence independently expand every polynomial. Separate finite
field linear algebra computes the augmentation-ideal filtration directly
from right regular group multiplication for selected small matrix/triple-law
groups. Cyclic groups C2, C3 and C5 are controls with known Hilbert series.
Use --stored to check the authenticated draft fetched from the API; the default
checks the generator. With --stored, NUMBERDB_KEY_FROM_STDIN=1 reads a token.
The repository's check.py is attached unchanged for its reusable exactness,
length, ring-import and comparison checks.
"""
import json
import os
import sys
from itertools import product
from pathlib import Path
import numberdb.sage as numberdb
from sage.rings.integer_ring import ZZ
from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
from sage.rings.finite_rings.finite_field_constructor import GF
from sage.matrix.constructor import matrix
import check
from generate import AugmentationHilbert

R = PolynomialRing(ZZ, 't')


def independent_coefficients(identity):
    family, size, p = identity.split('-')
    size, p = int(size), int(p)
    weights = ([j-i for i in range(size) for j in range(i+1, size)]
               if family == 'UT' else [1 for _ in range(2*size)] + [2])
    degree = (p-1)*sum(weights)
    log_derivative = [0] + [sum(w for w in weights if k % w == 0)
        - sum(p*w for w in weights if k % (p*w) == 0)
        for k in range(1, degree+1)]
    coeff = [1]
    for m in range(1, degree+1):
        numerator = sum(log_derivative[k]*coeff[m-k] for k in range(1,m+1))
        assert numerator % m == 0
        coeff.append(numerator//m)
    return R(coeff)


def regular_permutations(family, size, p):
    if family == 'C':
        elements = list(range(p))
        return p, [[(g+1) % p for g in elements]]
    if family == 'UT':
        coordinates = [(i,j) for i in range(size) for j in range(i+1,size)]
        slots = {ij:k for k,ij in enumerate(coordinates)}
        elements = list(product(range(p), repeat=len(coordinates)))
        generators = []
        for k in range(size-1):
            s = [0]*len(coordinates); s[slots[k,k+1]]=1
            generators.append(tuple(s))
        def multiply(a,b):
            return tuple((a[slots[i,j]]+b[slots[i,j]]+
                sum(a[slots[i,k]]*b[slots[k,j]] for k in range(i+1,j))) % p
                for i,j in coordinates)
    else:
        elements = list(product(range(p), repeat=2*size+1))
        generators = []
        for k in range(2*size):
            s=[0]*(2*size+1);s[k]=1;generators.append(tuple(s))
        def multiply(x,y):
            return tuple((x[k]+y[k]) % p for k in range(2*size)) + (
                (x[-1]+y[-1]+sum(x[k]*y[size+k] for k in range(size))) % p,)
    indices = {g:i for i,g in enumerate(elements)}
    permutations = [[indices[multiply(g,s)] for g in elements] for s in generators]
    for perm in permutations:
        assert sorted(perm) == list(range(len(elements)))
    # The specified matrices generate every element. This is checked by BFS,
    # so the filtration computation does not silently use a proper subgroup.
    reached = {0}; frontier = [0]
    while frontier:
        g = frontier.pop()
        for perm in permutations:
            if perm[g] not in reached:
                reached.add(perm[g]);frontier.append(perm[g])
    assert len(reached) == len(elements)
    return len(elements), permutations


def binary_basis(rows):
    basis = {}
    for row in rows:
        while row:
            pivot = row.bit_length()-1
            if pivot in basis:
                row ^= basis[pivot]
            else:
                basis[pivot] = row
                break
    return list(basis.values())


def augmentation_layers(family, size, p):
    order, perms = regular_permutations(family,size,p)
    dimensions = [order]
    if p == 2:
        basis = [1 << i for i in range(order)]
        while basis:
            def rows():
                for v in basis:
                    for perm in perms:
                        remaining=v; moved=0
                        while remaining:
                            bit=remaining & -remaining
                            moved |= 1 << perm[bit.bit_length()-1]
                            remaining ^= bit
                        yield moved ^ v
            basis = binary_basis(rows())
            dimensions.append(len(basis))
    else:
        field = GF(p)
        basis = [[int(i==j) for j in range(order)] for i in range(order)]
        while basis:
            rows=[]
            for v in basis:
                for perm in perms:
                    row=[-int(a) % p for a in v]
                    for i,a in enumerate(v):
                        row[perm[i]] = (row[perm[i]] + int(a)) % p
                    rows.append(row)
            echelon = matrix(field, rows).echelon_form()
            rank = echelon.rank()
            basis = [list(echelon.row(i)) for i in range(rank)]
            dimensions.append(rank)
    assert all(a>b for a,b in zip(dimensions,dimensions[1:]))
    return [a-b for a,b in zip(dimensions,dimensions[1:])], dimensions


def read_stored():
    if os.environ.get('NUMBERDB_KEY_FROM_STDIN') == '1':
        token=sys.stdin.read().strip()
        if '=' in token and token.split('=',1)[0].strip().isidentifier():
            token=token.split('=',1)[1].strip().strip('\"\'')
        os.environ['NUMBERDB_API_KEY']=token
    doc=numberdb.table('T653')
    numbers=doc['Numbers']
    if isinstance(numbers,list):
        return {x['params']['G']:R(x['number']) for x in numbers}
    return {k:R(v['number'] if isinstance(v,dict) else v) for k,v in numbers.items()}


def main():
    generator=AugmentationHilbert()
    values=read_stored() if '--stored' in sys.argv or os.environ.get('CHECK_STORED')=='1' else {
        x['G']:generator.value(x)['number'] for x in generator.enumerate()}
    expected=[x['G'] for x in generator.enumerate()]
    assert list(values)==expected and len(values)==25
    assert not check.exactness(values)
    assert not check.names_its_rings(Path(__file__).with_name('generate.py'))
    assert not check.agrees_with(values, independent_coefficients)
    evidence={'mode':'stored' if '--stored' in sys.argv or os.environ.get('CHECK_STORED')=='1' else 'generated',
        'all_entry_count':25,'measure':check.measure(values),'controls':{},'group_algebra_checks':{}}
    for name,H in values.items():
        family,s,p=name.split('-');s,p=int(s),int(p)
        degree=((p-1)*s*(s*s-1)//6 if family=='UT' else (p-1)*(2*s+2))
        order=p**(s*(s-1)//2 if family=='UT' else 2*s+1)
        assert H.degree()==degree and H(0)==1 and H.leading_coefficient()==1
        assert H(1)==order and all(a>0 for a in H.list())
        assert H.list()==H.list()[::-1]
        assert H[1]==(s-1 if family=='UT' else 2*s)
        assert 2*H.derivative()(1)==degree*order
    for p in (2,3,5):
        layers,dims=augmentation_layers('C',1,p)
        assert layers==[1]*p
        evidence['controls']['C%d'%p]={'layers':layers,'ideal_dimensions':dims}
    direct=['UT-3-2','UT-4-2','UT-5-2','Heis-2-2','Heis-3-2','Heis-4-2',
            'UT-3-3','UT-3-5','UT-3-7']
    for identity in direct:
        family,s,p=identity.split('-')
        layers,dims=augmentation_layers(family,int(s),int(p))
        assert R(layers)==values[identity], identity
        evidence['group_algebra_checks'][identity]={'layers':layers,'ideal_dimensions':dims}
        print('Direct augmentation filtration passed:',identity,flush=True)
    # Rank-one alias agrees as an independently defined triple-law group.
    for p in (2,3,5,7):
        assert independent_coefficients('Heis-1-%d'%p)==values['UT-3-%d'%p]
    coxeter=numberdb.table('T257')['Numbers']['B2']
    assert values['UT-3-2']==R(coxeter if isinstance(coxeter,str) else coxeter['number'])
    orders=numberdb.table('T258')['Numbers']['A']
    for name,H in values.items():
        family,n,p=name.split('-');n,p=int(n),int(p)
        if family!='UT':continue
        expr=orders[str(n-1)]
        if isinstance(expr,dict):expr=expr['number']
        P=R(expr.replace('q','t'))
        total=ZZ(P(p));part=1
        while total % p==0:
            part*=p;total//=p
        assert H(1)==part
    evidence['checks']=['independent log-derivative coefficient recurrence on all 25 entries',
        'nonnegative dimensions, endpoints, degree, order, symmetry, first layer, derivative at 1',
        'rank-one Heisenberg aliases for four characteristics',
        'live T257 B2 equality and all 13 live T258 A_(n-1) p-part checks']
    import sage.version
    evidence['versions']={'sage':sage.version.version,'numberdb':numberdb.__version__,'python':sys.version.split()[0]}
    evidence['skill_manifest']='agents/runs/20261101T152208Z-build-skills/manifest.json'
    evidence['skill_sha256']='a2b28d9953264d7b0ca62663785c2952190c5be6d6d3597fd695d9a54d606278'
    print('EVIDENCE_JSON='+json.dumps(evidence,sort_keys=True))


if __name__=='__main__':
    main()
