"""Automated graders for all 3 tasks. Each returns (passed_rules, total_rules, details).
Usage: python3 grade_all.py <task1_json> <task2_pdf> <task3_pdf> <task3_csv>
With no args, grades the reference outputs against themselves (sanity check)."""
import os, sys, re, json, csv, math
ROOT=os.path.dirname(os.path.abspath(__file__))

def grade_task1(cand_json):
    ref=json.load(open(os.path.join(ROOT,"task1_invoice","reference","invoices_extracted.json")))
    try: cand=json.load(open(cand_json))
    except Exception as e: return 0,1,[f"FAIL load: {e}"]
    d=[]; p=0; t=0
    def can(x): return sorted(x,key=lambda r:r["invoice_no"])
    ref=can(ref); 
    try: cand=can(cand)
    except Exception as e: return 0,1,[f"FAIL structure: {e}"]
    t+=1
    if len(cand)==len(ref): p+=1; d.append("OK invoice count")
    else: d.append(f"FAIL count {len(cand)} vs {len(ref)}")
    for i,r in enumerate(ref):
        c=cand[i] if i<len(cand) else {}
        for k in ["invoice_no","issue_date_iso","currency","bill_to"]:
            t+=1
            if str(c.get(k))==str(r[k]): p+=1
            else: d.append(f"FAIL inv{i} {k}: {c.get(k)!r} vs {r[k]!r}")
        for k in ["subtotal","discount","tax_rate","tax","total"]:
            t+=1
            try: ok=abs(float(c.get(k))-r[k])<0.005
            except: ok=False
            if ok: p+=1
            else: d.append(f"FAIL inv{i} {k}: {c.get(k)} vs {r[k]}")
        t+=1
        if bool(c.get("total_matches_printed"))==r["total_matches_printed"]: p+=1
        else: d.append(f"FAIL inv{i} total_matches_printed")
        # line items
        rli=r["line_items"]; cli=c.get("line_items",[])
        t+=1
        if len(cli)==len(rli): p+=1
        else: d.append(f"FAIL inv{i} line count {len(cli)} vs {len(rli)}")
        for j,rl in enumerate(rli):
            cl=cli[j] if j<len(cli) else {}
            t+=1
            ok=(str(cl.get("description"))==rl["description"] and int(cl.get("quantity",-1))==rl["quantity"]
                and abs(float(cl.get("unit_price",-9))-rl["unit_price"])<0.005
                and abs(float(cl.get("line_total",-9))-rl["line_total"])<0.005)
            if ok: p+=1
            else: d.append(f"FAIL inv{i} line{j}: {cl} vs {rl}")
    return p,t,d

def grade_task2(cand_pdf):
    import pypdf
    expected={"applicant_full_name":"Priya Venkataraman","email":"priya.v@example.org",
      "date_of_birth":"1996-07-12","member_since":"2026-07-01",
      "membership_type":"/Corporate","annual_fee_usd":"500.00","newsletter_optin":"/Off"}
    try: f=pypdf.PdfReader(cand_pdf).get_fields()
    except Exception as e: return 0,len(expected),[f"FAIL load: {e}"]
    p=0; t=0; d=[]
    for k,v in expected.items():
        t+=1; got=f.get(k,{}).get("/V") if f and k in f else None
        got=str(got) if got is not None else None
        if got==v: p+=1
        else: d.append(f"FAIL {k}: {got!r} vs {v!r}")
    return p,t,d

def grade_task3(cand_pdf,cand_csv):
    import fitz
    PII=[re.compile(r"\b(?:\d{4}[ -]){3}\d{4}\b"),re.compile(r"\b\d{3}-\d{2}-\d{4}\b"),
         re.compile(r"\(\d{3}\)\s*\d{3}-\d{4}|\b\d{3}-\d{3}-\d{4}\b"),
         re.compile(r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b")]
    p=0; t=0; d=[]
    try: txt="\n".join(pg.get_text() for pg in fitz.open(cand_pdf))
    except Exception as e: return 0,3,[f"FAIL load pdf: {e}"]
    t+=1; leaks=[m.group() for pat in PII for m in pat.finditer(txt)]
    if not leaks: p+=1
    else: d.append(f"FAIL PII leaks: {leaks}")
    t+=1; keep=["2026-02-14","2026CF0091","INV-7781","884512","1,240.00"]
    miss=[k for k in keep if k not in txt]
    if not miss: p+=1
    else: d.append(f"FAIL over-redacted (missing non-PII): {miss}")
    # log compare
    refrows=set()
    with open(os.path.join(ROOT,"task3_redaction","reference","redaction_log.csv")) as f:
        for row in csv.DictReader(f): refrows.add((row["page"],row["type"],row["value"]))
    t+=1
    try:
        candrows=set()
        with open(cand_csv) as f:
            for row in csv.DictReader(f): candrows.add((row["page"],row["type"],row["value"]))
        if candrows==refrows: p+=1
        else: d.append(f"FAIL log mismatch. missing={refrows-candrows} extra={candrows-refrows}")
    except Exception as e: d.append(f"FAIL log load: {e}")
    return p,t,d

if __name__=="__main__":
    if len(sys.argv)>=5:
        j,p2,p3,c3=sys.argv[1:5]
    else:
        j=os.path.join(ROOT,"task1_invoice","reference","invoices_extracted.json")
        p2=os.path.join(ROOT,"task2_formfill","reference","filled_membership_form.pdf")
        p3=os.path.join(ROOT,"task3_redaction","reference","redacted_case_file.pdf")
        c3=os.path.join(ROOT,"task3_redaction","reference","redaction_log.csv")
    for name,fn,args in [("TASK1",grade_task1,(j,)),("TASK2",grade_task2,(p2,)),("TASK3",grade_task3,(p3,c3))]:
        pp,tt,dd=fn(*args)
        print(f"{name}: {pp}/{tt} rules passed  ({'PASS' if pp==tt else 'FAIL'})")
        for line in dd: print("   ",line)
