#!/usr/bin/env python3
from pathlib import Path
import json,os,shutil,subprocess,sys,secrets,re,hashlib
from query_safety import validate_single_read_only_query
root=Path(__file__).resolve().parents[1]
status_path=root/'evidence/runtime-status.json'; evidence_dir=root/'evidence/runtime'; evidence_dir.mkdir(parents=True,exist_ok=True)
psql=shutil.which('psql'); url=os.environ.get('MAKNOW_DATABASE_URL'); confirm=os.environ.get('MAKNOW_DAT006_DISPOSABLE_CONFIRM')
def h(p): return hashlib.sha256(Path(p).read_bytes()).hexdigest()
def write_status(obj): status_path.write_text(json.dumps(obj,indent=2)+'\n'); print(json.dumps(obj,indent=2))
def base_status(status):
    return {'schema':'DAT006_RUNTIME_STATUS_v2','status':status,'postgresql_version':None,'fabricated_runtime_evidence':False,'required_for_learner_completion':True,'isolation':'EPHEMERAL_RANDOM_SCHEMA','generated_schema':None,'structural_contract_pass':False,'cleanup':'NOT_STARTED','input_sha256':{'query.sql':h(root/'sql/query.sql'),'schema.sql':h(root/'sql/schema.sql'),'seed.sql':h(root/'sql/seed.sql'),'model-contract.json':h(root/'model-contract.json')},'evidence_files':[]}
if not psql or not url:
    out=base_status('NOT_RUN_MISSING_REAL_POSTGRESQL_RUNTIME'); write_status(out); raise SystemExit(3)
if confirm!='YES':
    out=base_status('NOT_RUN_DISPOSABLE_CONFIRMATION_REQUIRED'); write_status(out); raise SystemExit(4)
query=(root/'sql/query.sql').read_text().strip(); ok,issues=validate_single_read_only_query(query)
if not ok:
    out=base_status('BLOCKED_UNSAFE_OR_NONSELECT_QUERY'); out['safety_issues']=issues; write_status(out); raise SystemExit(5)
if 'TODO:' in query:
    out=base_status('BLOCKED_LEARNER_SCAFFOLD_INCOMPLETE'); write_status(out); raise SystemExit(6)
starter=(root/'sql/starter_broken.sql').read_text().strip(); sok,sissues=validate_single_read_only_query(starter)
if not sok: raise SystemExit('PACKAGED_STARTER_FAILED_READ_ONLY_GUARD:'+','.join(sissues))
schema_name='maknow_dat006_'+secrets.token_hex(6)
if not re.fullmatch(r'maknow_dat006_[0-9a-f]{12}',schema_name): raise SystemExit('UNSAFE_GENERATED_SCHEMA')
q=query.rstrip().rstrip(';'); starter_q=starter.rstrip().rstrip(';')
def proc(sql,tuples=False):
    args=[psql,'-X',url,'-v','ON_ERROR_STOP=1']
    if tuples: args += ['-At','-F','\t','-P','null=__NULL__']
    args += ['-c',sql]
    return subprocess.run(args,text=True,capture_output=True)
def scoped(sql): return f'SET search_path TO "{schema_name}";\n'+sql
def require(p,label):
    if p.returncode!=0: raise RuntimeError(label+': '+p.stderr.strip())
    return p
status=base_status('RUNTIME_FAILED'); status['generated_schema']=schema_name; created=False
try:
    require(proc(f'CREATE SCHEMA "{schema_name}";'),'CREATE_SCHEMA'); created=True
    for f in ['schema.sql','seed.sql']:
        require(proc(scoped((root/'sql'/f).read_text())),f.upper())
    ver=require(proc('SHOW server_version;',True),'VERSION')
    status['postgresql_version']=ver.stdout.strip().splitlines()[-1] if ver.stdout.strip() else None
    # Negative observation from deliberately broken starter.
    broken=require(proc(scoped(starter_q),True),'STARTER_QUERY')
    (evidence_dir/'starter-output.tsv').write_text(broken.stdout)
    repaired=require(proc(scoped(q),True),'REPAIRED_QUERY')
    (evidence_dir/'query-output.tsv').write_text(repaired.stdout)
    # Structural contract: same number of rows as customers, unique and complete customer ids, required result values present.
    contract_sql=('WITH learner AS ('+q+')\nSELECT (SELECT COUNT(*) FROM customers), COUNT(*), COUNT(DISTINCT customer_id), COUNT(*) FILTER (WHERE customer_id IS NULL), COUNT(*) FILTER (WHERE recent_spend IS NULL), COUNT(*) FILTER (WHERE tier_rank IS NULL), (SELECT COUNT(*) FROM customers c WHERE NOT EXISTS (SELECT 1 FROM learner l WHERE l.customer_id=c.id)) FROM learner;')
    cr=require(proc(scoped(contract_sql),True),'STRUCTURAL_CONTRACT')
    (evidence_dir/'runtime-contract.tsv').write_text(cr.stdout)
    vals=cr.stdout.strip().split('\t')
    if len(vals)!=7: raise RuntimeError('STRUCTURAL_CONTRACT_UNPARSEABLE')
    nums=[int(x) for x in vals]
    expected_customers,rows,distinct_ids,null_ids,null_spend,null_rank,missing=nums
    structural=(rows==expected_customers==distinct_ids and null_ids==0 and null_spend==0 and null_rank==0 and missing==0)
    status['structural_contract']={'expected_customers':expected_customers,'output_rows':rows,'distinct_customer_ids':distinct_ids,'null_customer_ids':null_ids,'null_recent_spend':null_spend,'null_tier_rank':null_rank,'missing_customers':missing}
    status['structural_contract_pass']=structural
    if not structural: raise RuntimeError('STRUCTURAL_CONTRACT_FAILED')
    exp=require(proc(scoped('EXPLAIN (FORMAT JSON, COSTS TRUE) '+q+';'),True),'EXPLAIN_ESTIMATE')
    (evidence_dir/'explain-estimate.json').write_text(exp.stdout)
    ana=require(proc(scoped('EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON) '+q+';'),True),'EXPLAIN_ANALYZE')
    (evidence_dir/'explain-analyze.json').write_text(ana.stdout)
    status['status']='REAL_RUNTIME_EVIDENCE_CAPTURED_CONTRACT_PASS'
    status['evidence_files']=['runtime/starter-output.tsv','runtime/query-output.tsv','runtime/runtime-contract.tsv','runtime/explain-estimate.json','runtime/explain-analyze.json']
    status['hidden_expected_result_validation']='NOT_PERFORMED_BY_LEARNER_RUNNER'
except Exception as e:
    status['error_class']=type(e).__name__; status['error']=str(e)[:1000]
finally:
    if created:
        cp=proc(f'DROP SCHEMA IF EXISTS "{schema_name}" CASCADE;')
        status['cleanup']='PASS' if cp.returncode==0 else 'FAIL'
    else: status['cleanup']='NOT_NEEDED'
    write_status(status)
if status['status']!='REAL_RUNTIME_EVIDENCE_CAPTURED_CONTRACT_PASS' or status['cleanup']!='PASS': raise SystemExit(7)
