"""Current-model read-only regression before/after explicit validation and refresh.
Does not edit TM/QM/dictionary, register bundles, change data or restart Runtime.
"""
from pathlib import Path
from datetime import datetime
from decimal import Decimal
from collections import defaultdict
import subprocess,json,hashlib,sqlite3

LAB=Path(__file__).resolve().parents[1]
ROOT=LAB/'evidence/chapter-16'
E=ROOT/datetime.now().strftime('%Y%m%d-%H%M%S-%f');E.mkdir(parents=True)
Q=LAB/'queries/chapter-16';CLI=LAB/'tools/cli/Scripts/foggy-runtime.exe'
MODELS=['ChinookInvoiceQueryModel','ChinookInvoiceTimeQueryModel','ChinookInvoiceLineAggregateJoinQueryModel']
D=lambda x:Decimal(str(x))
def call(label,*args,required=True):
    p=subprocess.run([str(CLI),'--output','json','--base-url','http://127.0.0.1:18067','--namespace','chinook',*map(str,args)],capture_output=True,text=True,encoding='utf-8')
    (E/(label+'.json')).write_text(p.stdout,encoding='utf-8')
    if p.stderr:(E/(label+'.stderr.txt')).write_text(p.stderr,encoding='utf-8')
    o=json.loads(p.stdout)
    with (E/'commands.jsonl').open('a',encoding='utf-8') as f:f.write(json.dumps({'label':label,'args':list(map(str,args)),'exitCode':p.returncode,'success':o.get('success')},ensure_ascii=False)+'\n')
    if required:assert p.returncode==0 and o.get('success'),(label,o)
    return o
def hashes():
    return {str(p.relative_to(LAB)):hashlib.sha256(p.read_bytes()).hexdigest() for p in [*sorted((LAB/'models').rglob('*')),LAB/'data/Chinook_Sqlite.sqlite'] if p.is_file()}
bank=json.loads((Q/'question-bank.json').read_text(encoding='utf-8'))
(E/'queries').mkdir()
for p in Q.glob('*.json'):(E/'queries'/p.name).write_bytes(p.read_bytes())
def run_bank(phase):
    results={}
    for c in bank['cases']:
        if c['status']!='executable':continue
        name=c['id'];args=(c['model'],'--payload',Q/c['payloadFile'])
        v=call(phase+'-'+name+'-validate','query','validate',*args,required=False)
        if c.get('expectedRejection'):
            assert not v['success'] and v['error']['code']=='FIELD_NOT_FOUND' and 'averageAmount' in v['error']['message'];continue
        assert v['success'],(name,v)
        o=call(phase+'-'+name+'-execute','query','execute',*args)
        results[name]=o['data']['items'];print(phase,name,len(results[name]),flush=True)
    return results
def near(a,b):assert a is not None and abs(D(a)-D(b))<D('1e-8'),(a,str(b))
def verify(results,phase_name):
    con=sqlite3.connect((LAB/'data/Chinook_Sqlite.sqlite').as_uri()+'?mode=ro',uri=True)
    inv=[(i,c,dt,country,D(v)) for i,c,dt,country,v in con.execute('select InvoiceId,CustomerId,InvoiceDate,BillingCountry,Total from Invoice') if dt.startswith('2012-')]
    total=sum((x[4] for x in inv),D(0));count=len(inv);ids={x[0] for x in inv};countries=defaultdict(list);months=defaultdict(list)
    for x in inv:countries[x[3]].append(x[4]);months[x[2][:7]].append(x[4])
    a=results['01-totals'];assert len(a)==1 and a[0]['billCount']==count==83 and a[0]['buyers']==len({x[1] for x in inv})==47;near(a[0]['amount'],total)
    near(results['02-average'][0]['averageAmount'],total/count)
    usa=sorted(x for x in inv if x[3]=='USA');a=results['03-usa'];assert len(a)==len(usa)==21
    for r,x in zip(a,usa):assert r['invoiceId']==x[0] and r['billingCountry']=='USA';near(r['invoiceAmount'],x[4])
    near(sum((D(x['invoiceAmount']) for x in a),D(0)),'127.98')
    a=results['04-country-average'];assert len(a)==len(countries)==20
    for r,k in zip(a,sorted(countries)):
        assert r['billingCountry']==k and r['billCount']==len(countries[k]);near(r['amount'],sum(countries[k],D(0)));near(r['averageAmount'],sum(countries[k],D(0))/len(countries[k]))
    a=results['05-months'];assert len(a)==len(months)==12
    for r,k in zip(a,sorted(months)):assert r['invoiceDate$yearMonth']==k;near(r['amount'],sum(months[k],D(0)))
    ls=[(D(p),n) for i,p,n in con.execute('select InvoiceId,UnitPrice,Quantity from InvoiceLine') if i in ids]
    r=results['06-join'][0];assert r['billCount']==83 and r['lineCount']==len(ls)==447 and r['quantity']==sum(x[1] for x in ls)
    near(r['invoiceTotal'],total);near(r['detailTotal'],sum((p*n for p,n in ls),D(0)))
    assert all(r.get('averageAmount') is None for r in results['07-empty'])
    assert results['09-dict-label']==results['03-usa']
    expected=[x[0] for x in usa]
    for name in ['10-dict-display','11-dict-caption-filter']:
        assert [r['invoiceId'] for r in results[name]]==expected
        assert all(r['billingCountry']=='USA' for r in results[name])
    assert all(r['billingCountry$caption']=='美国' for r in results['10-dict-display'])
    assert [r['invoiceId'] for r in results['12-dict-in']]==sorted(x[0] for x in inv if x[3] in ['USA','Canada'])
    near(sum((x[4] for x in inv if x[3] in ['USA','Canada']),D(0)),'170.55')
    assert results['13-dict-unknown']==[]
    for name in ['09-dict-label','11-dict-caption-filter']:
        o=json.loads((E/(phase_name+'-'+name+'-execute.json')).read_text(encoding='utf-8'))
        assert o['data']['debug']['extra']['params'][-1]=='USA'
    con.close()

before=hashes();(E/'source-before.json').write_text(json.dumps(before,indent=2),encoding='utf-8')
for p in (LAB/'models').rglob('*'):
    if p.is_file():
        out=E/'source'/p.relative_to(LAB/'models');out.parent.mkdir(parents=True,exist_ok=True);out.write_bytes(p.read_bytes())
call('capabilities','capabilities');call('bundles','bundles','list')
for m in MODELS:call('baseline-metadata-'+m,'models','describe',m)
a=run_bank('baseline')
v=call('models-validate','models','validate','--models-dir',LAB/'models')
r=call('models-refresh','models','refresh','--model','ChinookInvoiceQueryModel')
for m in MODELS:call('refreshed-metadata-'+m,'models','describe',m)
md=call('refreshed-markdown','models','describe','ChinookInvoiceQueryModel','--format','markdown')
(E/'metadata.md').write_text(md['data']['content'],encoding='utf-8')
b=run_bank('refreshed')
for phase_name,result in [('baseline',a),('refreshed',b)]:verify(result,phase_name)
assert a==b and before==hashes()
for phase in ['baseline','refreshed']:
    for m in MODELS:
        x=json.loads((E/(phase+'-metadata-'+m+'.json')).read_text(encoding='utf-8'))['data']['data']['fields']
        if m==MODELS[0]:
            assert x['averageAmount']['name']=='平均每张账单金额' and '不是除以购买客户数' in x['averageAmount']['description']
            assert len(x['billingCountry']['dictItems'])==24 and x['billingCountry']['dictId']=='chinook_billing_country'
            assert 'billingCountry$caption' in x
    assert not (E/(phase+'-08-invalid-field-execute.json')).exists()
report={'runtime':'http://127.0.0.1:18067','namespace':'chinook','launcher':'0.1.30','cli':'0.1.25','comparison':'current baseline versus explicit validate/refresh; no model edit','positiveCasesPerPhase':12,'expectedFieldRejectionsPerPhase':1,'manualAnswerCasesNotExecuted':2,'independentSqliteDecimalCheck':True,'sameResultsAcrossRefresh':True,'allModelResourcesAndDatabaseUnchanged':True,'modelFilesValidated':v['data']['totalFiles'],'refreshedCount':r['data']['refreshedCount'],'evidenceDirectory':str(E),'cleanConversationTested':False}
(E/'acceptance.json').write_text(json.dumps(report,ensure_ascii=False,indent=2),encoding='utf-8')
(ROOT/'latest.json').write_text(json.dumps(report,ensure_ascii=False,indent=2),encoding='utf-8')
print(json.dumps(report,ensure_ascii=True,indent=2))
