#!/usr/bin/env python3
import hashlib
import json
import sys
from datetime import datetime, timezone
from pathlib import Path

PROJECT = Path(__file__).resolve().parents[1]
LOG = PROJECT / "log"
OUTPUT = PROJECT / "output"
MANIFEST = PROJECT / "script/project-manifest.json"
COMPLETION = LOG / "completion-report.json"
MEDIA = LOG / "postiz-media.json"
SCHEDULE = LOG / "postiz-schedule.json"
TRANSCODE = LOG / "upload-transcode.json"
RECEIPT = LOG / "storage-cleanup.json"
UPLOAD = OUTPUT / "final-upload.mp4"
DELETE = [OUTPUT / "final.mp4", OUTPUT / "footage/footage.mp4", OUTPUT / "intro/intro.mp4", OUTPUT / "info.txt"]


def load(path):
    if not path.is_file():
        raise RuntimeError(f"missing {path.relative_to(PROJECT)}")
    return json.loads(path.read_text(encoding="utf-8"))


def sha(path):
    digest = hashlib.sha256()
    with path.open("rb") as handle:
        for block in iter(lambda: handle.read(8 * 1024 * 1024), b""):
            digest.update(block)
    return digest.hexdigest()


def main():
    completion = load(COMPLETION)
    media = load(MEDIA)
    schedule = load(SCHEDULE)
    transcode = load(TRANSCODE)
    manifest = load(MANIFEST)
    canon = completion.get("canon_sha256")
    media_video = (media.get("media") or {}).get("video") or {}
    posts = schedule.get("posts") or {}
    schedule_ok = schedule.get("verified") is True and schedule.get("status") == "completed" and len(posts) == 2
    timestamps = set()
    if schedule_ok:
        for rows in posts.values():
            schedule_ok = schedule_ok and len(rows) == 1 and rows[0].get("state") == "QUEUE"
            if rows:
                timestamps.add(rows[0].get("publishDate"))
        schedule_ok = schedule_ok and len(timestamps) == 1 and None not in timestamps
    gates = {
        "completion": completion.get("verified") is True and completion.get("checks_passed") == completion.get("checks_total"),
        "manifest_completed": manifest.get("status") == "completed",
        "media": media.get("verified") is True and media.get("source_canon_sha256") == canon,
        "schedule": schedule_ok and schedule.get("source_canon_sha256") == canon,
        "transcode": transcode.get("verified") is True and transcode.get("status") == "completed" and transcode.get("source_canon_sha256") == canon and transcode.get("server_verification", {}).get("under_one_gb") is True,
        "upload_exists": UPLOAD.is_file(),
    }
    if not all(gates.values()):
        raise RuntimeError("cleanup gates failed: " + ", ".join(key for key, value in gates.items() if not value))
    upload_hash = sha(UPLOAD)
    upload_bytes = UPLOAD.stat().st_size
    if upload_bytes >= 1_000_000_000 or transcode.get("output_sha256") != upload_hash or media_video.get("sha256") != upload_hash or int(media_video.get("bytes", -1)) != upload_bytes:
        raise RuntimeError("retained upload copy size/hash mismatch")
    if RECEIPT.exists():
        prior = json.loads(RECEIPT.read_text(encoding="utf-8"))
        if prior.get("verified") is True and prior.get("status") == "completed":
            leftovers = [str(path.relative_to(PROJECT)) for path in OUTPUT.rglob("*") if path.is_file() and path != UPLOAD]
            if not leftovers and UPLOAD.is_file() and sha(UPLOAD) == prior.get("retained", {}).get("sha256"):
                print(json.dumps({"verified": True, "resumed": True, "freed_bytes": prior.get("freed_bytes")}, ensure_ascii=False))
                return 0
            raise RuntimeError("completed cleanup receipt conflicts with current output files")
    prior_failed = json.loads(RECEIPT.read_text(encoding="utf-8")) if RECEIPT.exists() else {}
    rows_by_path = {
        row["path"]: row
        for row in prior_failed.get("deleted_candidates", [])
        if isinstance(row, dict) and row.get("path") and row.get("bytes") is not None and row.get("sha256")
    }
    for path in DELETE:
        if path.is_file():
            row = {"path": str(path.relative_to(PROJECT)), "bytes": path.stat().st_size, "sha256": sha(path)}
            rows_by_path[row["path"]] = row
    rows = list(rows_by_path.values())
    running = {
        "version": 1, "verified": False, "status": "running", "project_id": PROJECT.name,
        "source_canon_sha256": canon, "gate_checks": gates, "deleted_candidates": rows,
        "retained": {"path": str(UPLOAD.relative_to(PROJECT)), "bytes": upload_bytes, "sha256": upload_hash},
        "started_at": datetime.now(timezone.utc).isoformat(),
    }
    RECEIPT.write_text(json.dumps(running, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
    for path in DELETE:
        if path.is_file():
            path.unlink()
    leftovers = [str(path.relative_to(PROJECT)) for path in OUTPUT.rglob("*") if path.is_file() and path != UPLOAD]
    verified = not leftovers and UPLOAD.is_file() and sha(UPLOAD) == upload_hash
    running.update({"verified": verified, "status": "completed" if verified else "failed", "freed_bytes": sum(row["bytes"] for row in rows), "output_leftovers": leftovers, "completed_at": datetime.now(timezone.utc).isoformat()})
    RECEIPT.write_text(json.dumps(running, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
    if not verified:
        raise RuntimeError("post-delete verification failed")
    manifest.setdefault("steps", {})["storage_cleanup"] = "completed"
    manifest["storage_cleanup"] = {"verified": True, "receipt": "log/storage-cleanup.json", "freed_bytes": running["freed_bytes"]}
    manifest["updated_at"] = running["completed_at"]
    MANIFEST.write_text(json.dumps(manifest, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
    print(json.dumps({"verified": True, "freed_bytes": running["freed_bytes"], "retained": running["retained"]}, ensure_ascii=False))
    return 0


if __name__ == "__main__":
    try:
        sys.exit(main())
    except Exception as exc:
        print(f"Storage cleanup blocked: {exc}", file=sys.stderr)
        sys.exit(1)
