"""Route a supplied report CSV. A CSV alone does not prove live provenance.
No network calls, database calls, external actions or automatic writes.
"""
import csv, hashlib, json, sys
from pathlib import Path

def blank(value):
    return value is None or value.strip().lower() in ('', 'null', '(null)', '<null>')

def optional_int(value):
    if blank(value): return None
    return int(value)

def read_rows(path):
    with Path(path).open(encoding='utf-8-sig', newline='') as f:
        reader=csv.DictReader(f)
        names=reader.fieldnames or []
        normalized=[x.split('.')[-1].strip('" ') for x in names]
        if len(normalized)!=len(set(normalized)) or not set(REQUIRED).issubset(normalized):
            raise ValueError('Missing or ambiguous report columns')
        rows=[]; seen=set()
        for raw in reader:
            if None in raw: raise ValueError('CSV row has extra fields')
            row={new:raw[old] for old,new in zip(names,normalized)}
            if any(row[k] is None for k in REQUIRED): raise ValueError('CSV row has missing fields')
            key=row[KEY]
            if blank(key) or key in seen: raise ValueError('Missing or duplicate report key')
            seen.add(key); rows.append(row)
    if not rows: raise ValueError('Empty report cannot verify this fixture')
    return rows

def route(path):
    path=Path(path)
    return {'input_sha256':hashlib.sha256(path.read_bytes()).hexdigest(),
            'source':'Supplied CSV; link actual console export evidence separately',
            'database_provenance_verified':False, 'external_actions_sent':False,
            'result':transform(read_rows(path))}

REQUIRED=['enrollment_id', 'course_id', 'resolved_course_id', 'reference_state']
KEY='enrollment_id'

def transform(rows):
    result={'ready':[],'held':[]}
    for row in sorted(rows,key=lambda x:x['enrollment_id']):
        state=row['reference_state']; parent=row['course_id']; resolved=row['resolved_course_id']; key=row['enrollment_id']
        if state=='optional_null':
            # CSV blank/null text is accepted as null only with the SQL IS NULL classification.
            if not blank(parent) or not blank(resolved): raise ValueError('Optional null has an unexpected reference')
            result['ready'].append(key)
        elif state=='resolved':
            if blank(parent) or parent!=resolved: raise ValueError('Resolved parent does not match')
            result['ready'].append(key)
        elif state=='missing_parent':
            if not blank(resolved): raise ValueError('Held reference unexpectedly resolved')
            # Preserve even an empty string as a missing reference, never as an optional null.
            result['held'].append({'id':key,'reason':'missing_parent','parent':parent})
        else: raise ValueError('Unknown reference state')
    return result

if __name__=='__main__':
    if len(sys.argv)!=2: raise SystemExit('Usage: python route-export.py report.csv')
    try: print(json.dumps(route(sys.argv[1]),indent=2))
    except (ValueError,KeyError): raise SystemExit('Report failed the declared CSV contract; inspect the source fields.')
