"""Exact checks for numberdb.org/T562; run with sage -python checks.py.

Uses the standard campaign check.py for exactness, size and independent
comparison. The mathematical checks use fractions.Fraction independently of
the generator's Sage arithmetic. See source-notes.md for theorem locators.
Optional --stored reads the authenticated draft and T561 from NumberDB;
otherwise T561.json is the archived companion input. Both paths compare all
25 values. Read a token from stdin with NUMBERDB_KEY_FROM_STDIN=1.
"""
import json
import os
import sys
from pathlib import Path
from fractions import Fraction as F
import numberdb.sage as numberdb
from sage.rings.integer_ring import ZZ
from sage.rings.rational_field import QQ
from sage.rings.real_arb import RealBallField
from sage.env import SAGE_VERSION
from generate import LyapunovSums, hyperelliptic_sum
from check import exactness, measure, agrees_with, names_its_rings

W = Path(__file__).resolve().parent


def rational(value):
    v = F(str(value))
    return QQ(v.numerator) / QQ(v.denominator)


def kappa(mu):
    # The equivalent form m(m+2)/(m+1) = (m+1)-1/(m+1).
    return sum((F(m+1)-F(1,m+1) for m in mu), F(0))/12


def partitions(total, maximum=None):
    if not total:
        yield ()
        return
    for head in range(min(total, maximum or total), 0, -1):
        for tail in partitions(total-head, head):
            yield (head,)+tail


def components(g, mu):
    # KZ Theorems 1-2, independently enumerate all classified components.
    if g == 2:
        return ('hyp',)
    if g == 3:
        return ('hyp','odd') if mu in ((4,),(2,2)) else ('connected',)
    hyper = mu in ((2*g-2,), (g-1,g-1))
    if all(m % 2 == 0 for m in mu):
        return (('hyp',) if hyper else ()) + ('even','odd')
    return ('hyp','nonhyp') if hyper else ('connected',)


def signature(params):
    return (int(params['g']), tuple(map(int,params['mu'].split(','))), params['component'])


def document_values(document):
    block=document['Numbers']; out={}
    if isinstance(block,list):
        for row in block:
            out[signature(row['params'])] = str(row['number'])
    else:
        for g, profiles in block.items():
            for mu, comps in profiles.items():
                for c, row in comps.items():
                    out[(int(g),tuple(map(int,str(mu).split(','))),c)] = str(row['number'] if isinstance(row,dict) else row)
    return out


def hyperelliptic_cover(g, mu):
    # EKZ Corollary 1, before its closed forms: sphere quadratic orders.
    ds = [2*g-3]+[-1]*(2*g+1) if len(mu)==1 else [2*g-2]+[-1]*(2*g+2)
    assert sum(ds)==-4
    return sum((F(1,d+2) for d in ds if d%2), F(0))/4


def divisor_sum(mu, a, d, marked=()):
    # CM Props. 4.5,4.8. Divide the vanishing divisor intersection by C.lambda.
    # a + sum b_i/((m_i+1)L) - d*(12 - 12*kappa/L) = 0.
    return (sum((F(b,m+1) for m,b in marked), F(0))+12*d*kappa(mu))/(12*d-a)


def cylinder_sum(g, mu, rows):
    # EMZ's r=c*zeta(2); EKZ/Vorobets: L=kappa+2 sum(q*r)/(D-1).
    dimension=2*g+len(mu)-1
    weighted=sum((q*F(r) for q,r in rows),F(0))
    return kappa(mu)+2*weighted/(dimension-1)


def run(values, companion):
    # Controls must pass before using the independent methods.
    assert kappa((2,))==F(2,9)
    assert hyperelliptic_cover(2,(2,))==F(4,3)
    assert divisor_sum((4,),9,1)==F(8,5)
    # EMZ Example 14.7: only the cylinder configuration contributes, r=5/3.
    assert cylinder_sum(2,(2,),[(0,'10/3'),(1,'5/3')])==F(4,3)
    # Positive control for Arb's containment and finite-width checks.
    field=RealBallField(512)
    known=field(1)/3
    assert known.is_finite() and (QQ(1)/QQ(3) in known) and field(known.rad())<field(2)**(-500)
    assert not (QQ(1)/QQ(2) in known)

    expected=[(g,mu,c) for g in range(2,5) for mu in partitions(2*g-2) for c in components(g,mu)]
    assert list(values)==expected and len(values)==25
    assert len({(g,mu) for g,mu,c in values})==18
    assert not exactness(values) and not names_its_rings(str(W/'generate.py'))
    source={signature(r):F(r['number']) for r in json.loads((W/'source-column.json').read_text())}
    assert set(source)==set(values)
    assert not agrees_with(values,lambda key:rational(source[key]))
    for (g,mu,c),value in values.items():
        assert 1 < value < g and value>rational(kappa(mu))

    hyp={key:value for key,value in values.items() if key[2]=='hyp'}
    assert not agrees_with(hyp,lambda key:rational(hyperelliptic_cover(key[0],key[1])))
    for key,v in hyp.items():
        assert hyperelliptic_sum(key[0],len(key[1]))==v

    # Farkas Thm 4.6, r=1,s=2: normalize the psi coefficient to one.
    r,s=1,2
    a=F((r+2)*(r*r*s**3-r*r*s+2*r*s**3+6*r*s*s-2*r*s-8*r+s**3+6*s*s+3*s-8),2*(s+r+1)*(r*s+s-2))
    c=F(s+1,2)
    b=F((s-1)*(s+1)*(r+1)*(r+2)*(r*s+s+4),12*(s+r+1)*(r*s+s-2))
    assert (a/c,b/c)==(8,1)
    assert divisor_sum((3,3),8,1,[(3,-1),(3,-1)])==F(7,4)
    # Eight divisor-intersection calculations plus two odd-spin divisor slopes.
    cm={
      (3,(4,),'odd'): divisor_sum((4,),9,1),
      (3,(3,1),'connected'): divisor_sum((3,1),9,1),
      (3,(2,1,1),'connected'): divisor_sum((2,1,1),-1,0,[(2,1),(1,3)]),
      (4,(6,),'even'): divisor_sum((6,),30,4,[(6,60)]),
      (4,(6,),'odd'): divisor_sum((6,),8,1,[(6,4)]),
      (4,(5,1),'connected'): divisor_sum((5,1),8,1,[(5,4)]),
      # Farkas Thm 4.6 has positive psi coefficients; CM Eq. (8) misprints signs.
      (4,(3,3),'nonhyp'): divisor_sum((3,3),8,1,[(3,1),(3,1)]),
      (4,(3,2,1),'connected'): divisor_sum((3,2,1),-1,0,[(3,1),(2,1),(1,3)]),
    }
    for g,mu in [(3,(2,2)),(4,(2,2,2))]:
        slope=F(4*(g+8),g+2)
        cm[(g,mu,'odd')]=12*kappa(mu)/(12-slope)
    assert len(cm)==10
    disagreements=agrees_with({key:values[key] for key in cm},lambda key:rational(cm[key]))
    assert not disagreements,disagreements

    # EMZ Appendix B.2 p.100: q counts double arrows cyclically (end repeats start).
    even_rows=[(0,'180/37'),(1,'45/74'),(1,'225/37'),(0,'225/296'),(2,'5/37')]
    odd_rows=[(0,'252/31'),(1,'63/62'),(1,'144/31'),(2,'9/155'),(2,'16/155'),(0,'4/93'),(3,'1/186')]
    even=cylinder_sum(4,(2,2,2),even_rows)
    assert even==F(737,333) and even!=F(166,75)
    assert F(166,75)-even==F(1,8325)
    assert rational(even)==values[(4,(2,2,2),'even')]
    assert rational(cylinder_sum(4,(2,2,2),odd_rows))==values[(4,(2,2,2),'odd')]

    # Separate configuration reconstruction for H(3,3)^nonhyp.
    nonhyp_rows=[(1,'3699/1120'),(0,'64/5'),(1,'64/35'),(1,'27/80'),(0,'1/5'),(1,'1/32')]
    assert rational(cylinder_sum(4,(3,3),nonhyp_rows))==values[(4,(3,3),'nonhyp')]

    # All companion decimal values denote exact center +/- one last-place unit.
    # T561 derives its constants from EKZ; this is consistency, not independence.
    area=document_values(companion)
    assert set(area)==set(values)
    max_radius=field(0)
    for key,text in area.items():
        center=F(text); ulp=F(1,10**len(text.partition('.')[2]))
        interval=field(rational(center)); interval=interval.add_error(field(rational(ulp)))
        back=field(rational(kappa(key[1])))+field.pi()**2*interval/3
        assert back.is_finite() and field(back.rad())<field(10)**(-95)
        assert values[key] in back,key
        max_radius=max(max_radius,field(back.rad()))

    # The checker must detect a corrupted independently checked row.
    damaged={key:value for key,value in hyp.items()}
    first=next(iter(damaged)); damaged[first]+=1
    assert agrees_with(damaged,lambda key:rational(hyperelliptic_cover(key[0],key[1])))
    # And an omitted component must not pass completeness.
    assert list(values)[:-1]!=expected
    return {'entries':len(values),'profiles':18,'exact_source_matches':25,
            'hyperelliptic_cover_checks':6,'CM_geometric_checks':10,
            'EMZ_configuration_checks':3,'additional_check_unique_entries':17,
            'companion_relation_checks':25,'companion_max_radius':str(max_radius),
            'source_disagreement':{'EKZ':'737/333','CM_section_6_9':'166/75',
             'EMZ_reconstruction':str(even),'difference_CM_minus_EMZ':'1/8325'},
            'controls':'passed, including corruption rejection','measurement':measure(values),
            'sage':SAGE_VERSION,'python':sys.version.split()[0],
            'skill_manifest':'agents/runs/20261027T213908Z-build-skills/manifest.json',
            'skill_sha256':'e37efb7e2ededf6c1d7134c673f152f648f26e0267c556aebdf3ec65765d8b4c'}


if __name__=='__main__':
    if os.environ.get('NUMBERDB_KEY_FROM_STDIN')=='1':
        numberdb.configure(api_key=sys.stdin.read().strip())
    if '--stored' in sys.argv:
        document=numberdb.table('T562')
        values={key:rational(v) for key,v in document_values(document).items()}
        companion=numberdb.table('T561')
        mode='stored'
    else:
        gen=LyapunovSums(); values={}
        for p in gen.enumerate():
            v=gen.value(p,100); values[signature(p)]=v['number'] if isinstance(v,dict) else v
        companion=json.loads((W/'T561.json').read_text()); mode='generated'
    result=run(values,companion); result['mode']=mode
    print(json.dumps(result,indent=2))
