"""Deterministic graders for Router Gauntlet. grade(task, text) -> (passed: bool, detail: str)."""
import json, os, re, subprocess, tempfile

DOCKER = ["/opt/homebrew/bin/docker"]
ENV = {**os.environ, "DOCKER_HOST": f"unix://{os.path.expanduser('~')}/.colima/default/docker.sock"}

def answer(text):
    m = re.findall(r"<answer>(.*?)</answer>", text or "", re.S)
    a = m[-1] if m else (text or "").strip().splitlines()[-1:] and (text or "").strip().splitlines()[-1] or ""
    return re.sub(r"^```\w*|```$", "", a.strip()).strip()

def as_json(a):
    a = a[a.find("{"): a.rfind("}") + 1]
    return json.loads(a)

def norm_list(a): return [x.strip().strip("`'\".").lower() for x in a.split(",") if x.strip()]

def ints(a): return [int(x) for x in re.findall(r"-?\d+", a.replace(",", " , "))]

def check_sched(g, s):
    dur, M, hot = g["dur"], g["machines"], set(g["hot"])
    if set(s) != set(dur): return False, "missing/extra jobs"
    s = {k: int(v) for k, v in s.items()}
    if any(v < 0 for v in s.values()): return False, "negative start"
    for j, r in (g.get("rel") or {}).items():
        if s[j] < r: return False, f"{j} before release"
    for a, b in g["prec"]:
        if s[b] < s[a] + dur[a]: return False, f"precedence {a}->{b} violated"
    end = max(s[j] + dur[j] for j in dur)
    for t in range(end):
        run = [j for j in dur if s[j] <= t < s[j] + dur[j]]
        if len(run) > M: return False, f"{len(run)} jobs at t={t}"
        if len(hot & set(run)) > 1: return False, f"hot overlap at t={t}"
    return (end == g["optimal"]), f"makespan {end} (optimal {g['optimal']})"

HARNESS = """import json, sys
sys.setrecursionlimit(10000)
from solution import {func}
cases = json.load(open('/w/cases.json'))
ok = 0
for args, want in cases:
    try:
        if {func}(*args) == want: ok += 1
    except Exception:
        pass
print(json.dumps({{"ok": ok, "n": len(cases)}}))
"""

def run_code(g, text):
    m = re.findall(r"```(?:python|py)?\s*\n(.*?)```", text or "", re.S)
    if not m: return False, "no code block"
    code = max(m, key=len)
    with tempfile.TemporaryDirectory(dir=os.path.expanduser("~/tmp") if os.path.isdir(os.path.expanduser("~/tmp")) else None) as d:
        os.chmod(d, 0o755)
        open(f"{d}/solution.py", "w").write(code)
        open(f"{d}/harness.py", "w").write(HARNESS.format(func=g["func"]))
        cases = [[c[0] if isinstance(c[0], list) and g["func"].startswith("vault") else [c[0]], c[1]] for c in g["cases"]]
        json.dump(cases, open(f"{d}/cases.json", "w"))
        try:
            p = subprocess.run(DOCKER + ["run", "--rm", "--network", "none", "--memory", "256m", "--cpus", "1",
                               "--read-only", "--cap-drop", "ALL", "--user", "65534:65534", "-v", f"{d}:/w:ro", "-w", "/w",
                               "python:3-alpine", "timeout", "20", "python", "/w/harness.py"],
                               capture_output=True, text=True, timeout=60, env=ENV)
            r = json.loads(p.stdout.strip().splitlines()[-1])
        except Exception as e:
            return False, f"sandbox error: {str(e)[:120]} {p.stderr[-200:] if 'p' in dir() else ''}"
    return r["ok"] == r["n"], f"{r['ok']}/{r['n']} hidden tests"

def grade(task, text):
    k, g = task["kind"], task["gold"]
    try:
        if k == "code": return run_code(g, text)
        a = answer(text)
        if k == "json":
            j = as_json(a); ok = (j.get("vendor", "").strip() == g["vendor"] and int(j.get("total")) == g["total"] and j.get("due") == g["due"])
            return ok, a[:120]
        if k == "list": return norm_list(a) == [x.lower() for x in g], a[:120]
        if k == "int": v = ints(a); return bool(v) and v[-1] == g, a[:60]
        if k == "intlist": return ints(a) == g, a[:120]
        if k == "str": return " ".join(a.lower().split()) == g.lower(), a[:60]
        if k == "grid":
            j = as_json(a)
            ok = all({kk: str(vv).lower().strip() for kk, vv in j[h].items()} == {kk: vv.lower() for kk, vv in g[h].items()} for h in g)
            return ok, "grid " + ("ok" if ok else "wrong")
        if k == "sched": return check_sched(g, as_json(a))
    except Exception as e:
        return False, f"unparseable: {type(e).__name__}"
    return False, "unknown kind"
