"""Independent checks for numberdb.org/T689.

    sage -python checks.py
    sage -python checks.py --stored       # authenticated readback, no writes

Download generate.py, check.py and skill-manifest.json alongside this file.
The independent computation uses Python Fraction, nilpotent coefficient
products for the second Frobenius period, Lagrange inversion, and Mobius
inversion. It calls no generator arithmetic. Primary values are computed only
for comparison. This is independent code, not an independent mirror theorem.
The external geometric controls are the Macaulay2 documentation's localization
outputs, plus a Grassmannian top-Chern intersection calculation for lines.
"""
import json
import os
import shutil
import sys
from fractions import Fraction as F
from math import prod, gcd
from pathlib import Path
from itertools import combinations_with_replacement

import numberdb.sage as numberdb
from sage.rings.rational_field import QQ
from sage.env import SAGE_VERSION
import generate
from check import exactness, agrees_with, measure, names_its_rings

TYPES = ((5,), (2, 4), (3, 3), (2, 2, 3), (2, 2, 2, 2))
# rationalCurve documentation o3, o7, o11, o14, o15, o16; independently
# read from the source during this run. Listed numbers are the GV controls.
CONTROLS = {
    '5': [2875, 609250, 317206375, 242467530000],
    '2,4': [1280, 92288, 15655168, 3883902528],
    '3,3': [1053, 52812, 6424326, 1139448384],
    '2,2,3': [720, 22428, 1611504],
    '2,2,2,2': [512, 9728, 416256],
}
GW_CONTROLS = {
    '5': ['2875', '4876875/8', '8564575000/27', '15517926796875/64'],
    '2,4': ['1280', '92448', '422690816/27', '3883914084'],
    '3,3': ['1053', '423549/8', '6424365'],
    '2,2,3': ['720', '22518', '4834592/3'],
    '2,2,2,2': ['512', '9792', '11239424/27'],
}


def convolution(a, b, n):
    c = [F(0)]*(n+1)
    for i, x in enumerate(a):
        for j, y in enumerate(b[:max(0, n+1-i)]):
            c[i+j] += x*y
    return c


def quotient(a, b):
    c = []
    for k in range(len(a)):
        c.append((a[k] - sum(b[k-j]*c[j] for j in range(k))) / b[0])
    return c


def exp_by_powers(a, n):
    assert a[0] == 0
    result = [F(1)] + [F(0)]*n
    term = result[:]
    for k in range(1, n+1):
        term = [v/F(k) for v in convolution(term, a, n)]
        result = [x+y for x, y in zip(result, term)]
    return result


def mobius(n):
    primes = 0
    p = 2
    while p*p <= n:
        if n % p == 0:
            n //= p
            primes += 1
            if n % p == 0:
                return 0
            while n % p == 0:
                n //= p
        p += 1
    if n > 1:
        primes += 1
    return (-1)**primes


def independent(target, n):
    # A_m(epsilon)=prod (j+l epsilon) / prod (j+epsilon)^(r+4),
    # reduced modulo epsilon^3. No harmonic-sum formula is used here.
    columns = [[], [], []]
    for m in range(n+1):
        a = [F(1), F(0), F(0)]
        for l in target:
            for j in range(1, l*m+1):
                a = convolution(a, [F(j), F(l)], 2)
        for j in range(1, m+1):
            for _ in range(len(target)+4):
                a = convolution(a, [F(1,j), -F(1,j*j), F(1,j**3)], 2)
        for k in range(3):
            columns[k].append(a[k])
    h = quotient(columns[1], columns[0])
    b = quotient(columns[2], columns[0])
    hsquared = convolution(h, h, n)
    e = [v-w/F(2) for v, w in zip(b, hsquared)]
    derivative = [F(j)*e[j] for j in range(1,n+1)]
    # y2/y0 = (log q)^2/2 + E(x(q)); K=kappa*(q d/dq)^2(y2/y0).
    gw = [F(0)]
    for d in range(1,n+1):
        exponential = exp_by_powers([-F(d)*v for v in h[:d]], d-1)
        coefficient = sum(derivative[j]*exponential[d-1-j] for j in range(d))/F(d)
        gw.append(F(prod(target),d)*coefficient)
    gv = [F(0)] + [sum(F(mobius(k),k**3)*gw[d//k] for k in range(1,d+1) if d%k==0)
                     for d in range(1,n+1)]
    return gw, gv


def line_intersection(target, ambient=None):
    # Integrate top Chern of direct sum Sym^l S^* on Gr(2,r+4).
    # For symmetric f(u,v) of top degree, integral is
    # [u^(r+3) v^(r+2)] (u-v) f(u,v).
    poly = {(0,0): 1}
    for l in target:
        for j in range(l+1):
            new = {}
            for (a,b), value in poly.items():
                new[a+1,b] = new.get((a+1,b),0)+j*value
                new[a,b+1] = new.get((a,b+1),0)+(l-j)*value
            poly = new
    ambient = len(target)+3 if ambient is None else ambient
    return poly.get((ambient-1,ambient-1),0)-poly.get((ambient,ambient-2),0)


def template_check(target, n):
    f,g = generate.periods(target,n)
    alpha = [F(j,l) for l in target for j in range(1,l)]
    c=prod(l**l for l in target)
    a=F(1); h=F(0)
    for m in range(n+1):
        assert F(str(f[m])) == c**m*a
        assert F(str(g[m])) == c**m*a*h
        a *= prod(F(m)+v for v in alpha)/F((m+1)**4)
        h += sum(1/(v+m) for v in alpha)-F(4,m+1)
    keymap = {'5':'5','2,4':'2.2.4','3,3':'3.3','2,2,3':'2.2.3','2,2,2,2':'2.2.2.2'}
    key=','.join(map(str,target)); template=keymap[key]
    cyclotomic=[F(j,l) for l in map(int,template.split('.')) for j in range(1,l) if gcd(j,l)==1]
    assert sorted(alpha)==sorted(cyclotomic)
    return {'template':template,'C':c,'kappa':prod(target),'coefficients_checked':n+1}


def run(stored=False):
    # Known-answer controls precede comparisons, including nonzero geometry
    # outside the Calabi–Yau class: 27 lines on a cubic surface.
    assert line_intersection((3,), ambient=3) == 27
    # A known CY answer controls both a computational and geometric route.
    assert line_intersection((5,)) == 2875
    assert independent((5,),2)[1][1:] == [F(2875),F(609250)]
    assert [mobius(n) for n in range(1,11)] == [1,-1,-1,0,-1,1,-1,0,0,1]
    assert exp_by_powers([F(0),F(1),F(0),F(0)],3) == [F(1),F(1),F(1,2),F(1,6)]
    assert convolution([F(1),F(2)],[F(3),F(4)],2)==[F(3),F(10),F(8)]
    assert quotient([F(1),F(1),F(1)],[F(1),F(-1),F(0)])==[F(1),F(2),F(3)]
    assert generate.TARGETS==TYPES
    partition_types=[]
    for r in range(1,5):
        partition_types.extend(t for t in combinations_with_replacement(range(2,6),r) if sum(l-1 for l in t)==4)
    assert set(partition_types)==set(TYPES)
    generator=generate.GenusZeroGV()
    params=list(generator.enumerate())
    assert len(params)==100 and len({tuple(p.items()) for p in params})==100
    wanted=[{'multidegree':','.join(map(str,t)), 'd':str(d)} for t in TYPES for d in range(1,21)]
    assert params==wanted
    source_values={}
    independent_values={}
    evidence={'table':'T689','skill_manifest':'skill-manifest.json','skill_manifest_sha256':__import__('hashlib').sha256(Path(__file__).with_name('skill-manifest.json').read_bytes()).hexdigest(),'software':{'sage':SAGE_VERSION,'python':sys.version.split()[0],'numberdb':numberdb.__version__,'Macaulay2_available':bool(shutil.which('M2'))},'control_tests':'passed','targets':{}}
    for target in TYPES:
        key=','.join(map(str,target))
        k,gw,gv=generate.coefficients(target,24)
        k2,gw2,gv2=generate.coefficients(target,28)
        assert k[:21]==k2[:21] and gw[:21]==gw2[:21] and gv[:21]==gv2[:21]
        igw,igv=independent(target,24)
        for d in range(1,21):
            ident=(key,str(d));source_values[ident]=gv[d];independent_values[ident]=QQ(igv[d].numerator)/QQ(igv[d].denominator)
            assert F(str(gw[d]))==igw[d]
            assert sum(F(str(gv[d//j]))/F(j**3) for j in range(1,d+1) if d%j==0)==igw[d]
            assert sum(F(e**3)*igv[e] for e in range(1,d+1) if d%e==0)==F(str(k[d]))
            assert igv[d].denominator==1
        assert gv[1:len(CONTROLS[key])+1]==CONTROLS[key]
        assert [F(str(v)) for v in gw[1:len(GW_CONTROLS[key])+1]]==list(map(F,GW_CONTROLS[key]))
        assert line_intersection(target)==gv[1]
        evidence['targets'][key]={'series_order_comparison':[24,28],'degrees_checked':20,'GV_localization_controls':len(CONTROLS[key]),'GW_localization_controls':len(GW_CONTROLS[key]),'Grassmannian_line_count':line_intersection(target),'template':template_check(target,24),'largest_value':str(gv[20])}
    assert not agrees_with(source_values,lambda key:independent_values[key])
    assert not exactness(source_values)
    assert not names_its_rings(Path(__file__).with_name('generate.py'))
    evidence['measure']=measure(source_values)
    evidence['all_values']={key:{str(d):str(source_values[key,str(d)]) for d in range(1,21)} for key in CONTROLS}
    if stored:
        document=numberdb.table('T689')
        sibling=numberdb.table('T688')
        actual=document['Numbers']; other=sibling['Numbers']
        assert list(actual)==list(CONTROLS)
        for target in TYPES:
            key=','.join(map(str,target))
            assert list(actual[key])==list(map(str,range(1,21)))
            for d in range(1,21):
                row=actual[key][str(d)];value=row['number'] if isinstance(row,dict) else row
                assert F(value)==F(str(independent_values[key,str(d)]))
                conv=sum(F(str(actual[key][str(d//j)]))/F(j**3) for j in range(1,d+1) if d%j==0)
                assert conv==F(other[key][str(d)])
        report=generator.verify(sample=None)
        assert report.ok
        evidence['readback']={'independent_values':100,'GW_companion_relations':100,'generator_verify':str(report)}
    else:
        sibling_path=Path(__file__).with_name('T688.json')
        if sibling_path.exists():
            other=json.loads(sibling_path.read_text())['Numbers']
            for target in TYPES:
                key=','.join(map(str,target))
                igw,_=independent(target,20)
                assert all(F(other[key][str(d)])==igw[d] for d in range(1,21))
            evidence['archived_T688_comparisons']=100
    print(json.dumps(evidence,indent=2,default=str))
    return evidence


if __name__=='__main__':
    if os.environ.get('NUMBERDB_KEY_FROM_STDIN')=='1':
        token=sys.stdin.read().strip()
        if '=' in token and token.split('=',1)[0].replace('_','').isalnum():token=token.split('=',1)[1].strip().strip('\"\'')
        numberdb.configure(api_key=token)
    run(stored='--stored' in sys.argv or os.environ.get('NUMBERDB_KEY_FROM_STDIN')=='1')
