"""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=['day_no', 'observed_day', 'event_count', 'completed_day', 'coverage_state', 'late_event_count', 'revision_label', 'revised_count']
KEY='day_no'

def transform(rows):
    days=[]; revised=None
    for row in sorted(rows,key=lambda x:int(x['day_no'])):
        day=optional_int(row['day_no']); observed=optional_int(row['observed_day']); completed=optional_int(row['completed_day']); count=optional_int(row['event_count'])
        if day is None or day<1 or (observed is not None and observed!=day) or (completed is not None and completed!=day):
            raise ValueError('Day grain is inconsistent')
        if (observed is None)!=(count is None) or (count is not None and count<0): raise ValueError('Observation count contract is inconsistent')
        if completed is not None and observed is None: raise ValueError('Completeness needs an explicit observed count in this fixture')
        state='complete' if completed is not None else 'partial' if observed is not None else 'unknown'
        if row['coverage_state']!=state: raise ValueError('Coverage state disagrees with source evidence')
        days.append({'day':day,'count':count,'state':state})
        added=optional_int(row['late_event_count']); changed=optional_int(row['revised_count'])
        if added is None:
            if changed is not None or not blank(row['revision_label']): raise ValueError('Revision lacks its late-event input')
        else:
            if day!=2 or count is None or added<0 or changed!=count+added or blank(row['revision_label']):
                raise ValueError('Revision is not a separately evidenced day-2 count')
            revised=changed
    if [d['day'] for d in days]!=list(range(1,8)) or revised is None: raise ValueError('Expected seven days and an explicit day-2 revision')
    return {'days':days,'revised_day_2':revised}

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.')
