from pathlib import Path
import argparse, datetime, hashlib, json, shutil, struct, time


FILES = ("customskin.tga", "customskin_region.tga")


def is_lmu_livery_directory(path):
    names=[part.casefold() for part in Path(path).parts]
    return len(names)>=3 and names[-3:]==["le mans ultimate","userdata","liveries"]


def sha(path):
    h=hashlib.sha256()
    with path.open("rb") as f:
        for chunk in iter(lambda:f.read(1024*1024),b""): h.update(chunk)
    return h.hexdigest()


def validate_tga(path):
    if not path.exists(): raise FileNotFoundError(path)
    raw=path.read_bytes()[:18]
    if len(raw)!=18: raise ValueError(f"Ongeldige TGA-header: {path.name}")
    w,h=struct.unpack("<HH",raw[12:16]); bpp=raw[16]
    payload=18+w*h*(bpp//8); trailing=path.stat().st_size-payload
    checks={"type2":raw[2]==2,"size4096":(w,h)==(4096,4096),"rgb24":bpp==24,"trailing":trailing in (0,26)}
    if not all(checks.values()): raise ValueError(f"TGA-validatie mislukt voor {path.name}: {checks}")
    return {"file":path.name,"sha256":sha(path),"checks":checks}


def atomic_copy(source,target):
    staged=target.with_suffix(target.suffix+".racecore-staging")
    shutil.copy2(source,staged)
    if sha(source)!=sha(staged):
        staged.unlink(missing_ok=True); raise IOError(f"Staging-hash mismatch: {source.name}")
    staged.replace(target)


def sync(cfg, reason="manual"):
    source=Path(cfg["sourceDirectory"]).resolve(); target=Path(cfg["lmuLiveryDirectory"]).resolve()
    workspace=Path(cfg["workspaceDirectory"]).resolve()
    if not is_lmu_livery_directory(target):
        raise ValueError(f"Dit is geen geldige LMU Livery-map: {target}")
    if not target.is_dir():
        raise FileNotFoundError(
            f"LMU Livery-map niet gevonden: {target}. Kies eerst de juiste Steam-library."
        )
    validations=[validate_tga(source/n) for n in FILES]
    current={n:sha(target/n) if (target/n).exists() else None for n in FILES}
    incoming={x["file"]:x["sha256"] for x in validations}
    changed=[n for n in FILES if current[n]!=incoming[n]]
    event={"timestamp":datetime.datetime.now(datetime.timezone.utc).isoformat(),"reason":reason,"incoming":incoming,"previous":current,"changed":changed}
    if not changed:
        event["status"]="no_change"
        append_log(workspace,event); return event

    stamp=datetime.datetime.now().strftime("%Y%m%d-%H%M%S-%f")
    backup=workspace/"backups"/stamp; backup.mkdir(parents=True,exist_ok=False)
    for n in FILES:
        if (target/n).exists(): shutil.copy2(target/n,backup/n)
    (backup/"manifest.json").write_text(json.dumps({"created":event["timestamp"],"reason":reason,"hashes":current},indent=2),encoding="utf-8")

    # Eerst beide stagingbestanden schrijven, dan pas de actieve bestanden vervangen.
    staged=[]
    try:
        for n in FILES:
            s=target/(n+".racecore-staging"); shutil.copy2(source/n,s)
            if sha(s)!=incoming[n]:
                raise IOError(f"Staging-validatie mislukt: {n}")
            staged.append((s,target/n))
        for s,t in staged: s.replace(t)
    finally:
        for s,_target in staged:
            s.unlink(missing_ok=True)
    installed={n:sha(target/n) for n in FILES}
    if installed!=incoming: raise IOError("Geïnstalleerde hashes wijken af")
    event.update({"status":"installed","backup":str(backup),"installed":installed})
    append_log(workspace,event); return event


def append_log(workspace,event):
    workspace.mkdir(parents=True,exist_ok=True)
    with (workspace/"live-link.jsonl").open("a",encoding="utf-8") as f:
        f.write(json.dumps(event,ensure_ascii=False)+"\n")
    (workspace/"last-event.json").write_text(json.dumps(event,indent=2,ensure_ascii=False),encoding="utf-8")


def main():
    ap=argparse.ArgumentParser(); ap.add_argument("config"); ap.add_argument("--watch",action="store_true"); ap.add_argument("--interval",type=float,default=1.0)
    args=ap.parse_args(); cfg=json.loads(Path(args.config).read_text(encoding="utf-8"))
    if not args.watch:
        print(json.dumps(sync(cfg),indent=2)); return
    last=None
    while True:
        source=Path(cfg["sourceDirectory"])
        state=tuple((source/n).stat().st_mtime_ns for n in FILES)
        if state!=last:
            print(json.dumps(sync(cfg,reason="file_watch"),indent=2),flush=True); last=state
        time.sleep(args.interval)


if __name__=="__main__": main()
