"""Local fictional learning reference. No website/database/cloud connection."""
import csv,json,sqlite3,hashlib,math,re,statistics,collections
from pathlib import Path

def rows(source,name):
 with (Path(source)/name).open(encoding='utf-8',newline='') as f:return list(csv.DictReader(f))

def de_pipeline(source,out):
 # Full snapshot with staged output; no append or inferred incremental semantics.
 from datetime import date
 order_rows=rows(source,'orders.csv');line_rows=rows(source,'order_lines.csv');products=rows(source,'products.csv')
 if len({r['order_id'] for r in order_rows})!=len(order_rows):raise ValueError('Duplicate order keys')
 if len({r['product_id'] for r in products})!=len(products):raise ValueError('Duplicate product keys')
 for r in order_rows:
  if not r['order_id'] or r['status'] not in ['completed','cancelled']:raise ValueError('Invalid order key/status')
  if not re.fullmatch(r'\d{4}-\d{2}-\d{2}',r['order_date']):raise ValueError('ISO calendar date required')
  date.fromisoformat(r['order_date'])
 orders={r['order_id']:r for r in order_rows};product_keys={r['product_id'] for r in products};accepted=[];rejected=[]
 for n,r in enumerate(line_rows,2):
  reasons=[]
  try:
   quantity=int(r['quantity']);price=int(r['unit_price_cents'])
   if quantity<=0 or price<0:reasons.append('invalid_domain')
  except ValueError:reasons.append('invalid_number');quantity=price=0
  if r['order_id'] not in orders:reasons.append('unmatched_order')
  if r['product_id'] not in product_keys:reasons.append('unmatched_product')
  if not r['line_id']:reasons.append('missing_line_id')
  if reasons:rejected.append({'row':n,'line_id':r['line_id'],'reasons':reasons});continue
  accepted.append(dict(r,value_cents=quantity*price,status=orders[r['order_id']]['status']))
 if len({r['line_id'] for r in accepted})!=len(accepted):raise ValueError('Duplicate line keys')
 completed=[r for r in accepted if r['status']=='completed']
 manifest={'source_lines':len(line_rows),'accepted_lines_all_statuses':len(accepted),'quarantined_lines':len(rejected),'completed_lines':len(completed),'completed_value_cents':sum(r['value_cents'] for r in completed),'source_hashes':{n:hashlib.sha256((Path(source)/n).read_bytes()).hexdigest() for n in ['orders.csv','order_lines.csv','products.csv']}}
 assert len(line_rows)==len(accepted)+len(rejected)
 (Path(out)/'prepared.json').write_text(json.dumps(accepted,indent=2)+'\n');(Path(out)/'rejects.json').write_text(json.dumps(rejected,indent=2)+'\n')
 return manifest

if __name__=='__main__':
    import argparse,platform
    parser=argparse.ArgumentParser();parser.add_argument('--source',default='.');parser.add_argument('--output',required=True);args=parser.parse_args()
    source=Path(args.source).resolve();out=Path(args.output).resolve()
    if source==out:raise ValueError('Source and output must differ')
    out.mkdir(parents=False,exist_ok=False)
    # Partial folders after an error are NOT successful; result.json is written last.
    result=de_pipeline(source,out)
    with (out/'result.json').open('x',encoding='utf-8') as handle:json.dump(result,handle,indent=2,allow_nan=False);handle.write(chr(10))
    print(json.dumps(result,indent=2,allow_nan=False))
