#!/usr/bin/env python3
"""Gallery API — delete + upload. Port 3099."""
from http.server import HTTPServer, BaseHTTPRequestHandler
import json, os, cgi, io, subprocess, mimetypes
from pathlib import Path

BASE = Path("/root/.openclaw/workspace/staging/competition")
TOKEN = "gallery-delete-2026"
ALLOWED_EXT = {'.jpg','.jpeg','.png','.webp','.gif','.svg'}

class Handler(BaseHTTPRequestHandler):
    def log_message(self, fmt, *args): pass

    def cors(self):
        self.send_header("Access-Control-Allow-Origin", "*")
        self.send_header("Access-Control-Allow-Methods", "POST, OPTIONS")
        self.send_header("Access-Control-Allow-Headers", "Content-Type, X-Token")

    def do_OPTIONS(self):
        self.send_response(200); self.cors(); self.end_headers()

    def auth(self):
        return self.headers.get("X-Token") == TOKEN

    def respond(self, code, data):
        body = json.dumps(data).encode()
        self.send_response(code)
        self.cors()
        self.send_header("Content-Type", "application/json")
        self.end_headers()
        self.wfile.write(body)

    def do_POST(self):
        if not self.auth():
            return self.respond(401, {"error": "unauthorized"})

        if self.path == "/delete":
            length = int(self.headers.get("Content-Length", 0))
            body = json.loads(self.rfile.read(length))
            files = body.get("files", [])
            brand = body.get("brand", "unknown")
            deleted, skipped = [], []
            for f in files:
                clean = f.lstrip("/").replace("..", "")
                target = BASE / clean
                if target.exists() and BASE in target.resolve().parents:
                    target.unlink(); deleted.append(clean)
                else:
                    skipped.append(clean)
            if deleted:
                repo = Path("/root/.openclaw/workspace/public")
                subprocess.run(["git", "add", "-A"], cwd=repo)
                subprocess.run(["git", "commit", "-m", f"curator: delete {len(deleted)} from {brand}"], cwd=repo)
                subprocess.run(["git", "push"], cwd=repo)
            return self.respond(200, {"deleted": deleted, "skipped": skipped})

        elif self.path.startswith("/upload"):
            ct = self.headers.get("Content-Type", "")
            length = int(self.headers.get("Content-Length", 0))
            body_data = self.rfile.read(length)
            # Parse brand from query string
            from urllib.parse import urlparse, parse_qs
            qs = parse_qs(urlparse(self.path).query)
            brand = qs.get("brand", ["unknown"])[0].replace("..", "").replace("/", "")
            # Parse multipart
            boundary = ct.split("boundary=")[-1].encode()
            parts = body_data.split(b"--" + boundary)
            saved = []
            for part in parts:
                if b"filename=" not in part: continue
                header, _, content = part.partition(b"\r\n\r\n")
                content = content.rstrip(b"\r\n--")
                hdr = header.decode(errors="ignore")
                fname_match = __import__("re").search(r'filename="([^"]+)"', hdr)
                if not fname_match: continue
                fname = Path(fname_match.group(1)).name
                ext = Path(fname).suffix.lower()
                if ext not in ALLOWED_EXT: continue
                dest_dir = BASE / brand / "assets"
                dest_dir.mkdir(parents=True, exist_ok=True)
                dest = dest_dir / fname
                dest.write_bytes(content)
                saved.append(f"{brand}/assets/{fname}")
            if saved:
                repo = Path("/root/.openclaw/workspace/public")
                subprocess.run(["git", "add", "-A"], cwd=repo)
                subprocess.run(["git", "commit", "-m", f"curator: upload {len(saved)} to {brand}"], cwd=repo)
                subprocess.run(["git", "push"], cwd=repo)
            return self.respond(200, {"saved": saved})

        elif self.path.startswith("/save-crop"):
            length = int(self.headers.get("Content-Length", 0))
            body = json.loads(self.rfile.read(length))
            brand = body.get("brand", "unknown").replace("..", "").replace("/","")
            filename = Path(body.get("filename","crop.jpg")).name
            data_url = body.get("dataUrl","")
            if not data_url.startswith("data:image"): 
                return self.respond(400, {"error": "invalid data"})
            import base64
            _, b64 = data_url.split(",", 1)
            img_bytes = base64.b64decode(b64)
            stem = Path(filename).stem
            ext = ".jpg"
            dest_dir = BASE / brand / "assets"
            dest_dir.mkdir(parents=True, exist_ok=True)
            dest = dest_dir / f"{stem}-crop{ext}"
            dest.write_bytes(img_bytes)
            repo = Path("/root/.openclaw/workspace/public")
            subprocess.run(["git", "add", "-A"], cwd=repo)
            subprocess.run(["git", "commit", "-m", f"curator: save crop {stem} in {brand}"], cwd=repo)
            subprocess.run(["git", "push"], cwd=repo)
            return self.respond(200, {"saved": f"{brand}/assets/{stem}-crop{ext}"})



        elif self.path == "/save-state":
            length = int(self.headers.get("Content-Length", 0))
            body = json.loads(self.rfile.read(length))
            brand = body.get("brand", "").replace("..", "").replace("/", "")
            state = body.get("state", {})
            dest = BASE / brand / "curate" / "state.json"
            dest.parent.mkdir(parents=True, exist_ok=True)
            dest.write_text(__import__("json").dumps(state, indent=2))
            repo = Path("/root/.openclaw/workspace/public")
            subprocess.run(["git", "add", str(dest)], cwd=repo)
            subprocess.run(["git", "commit", "-m", f"curate: save state for {brand}"], cwd=repo)
            subprocess.run(["git", "push"], cwd=repo)
            return self.respond(200, {"saved": True, "path": str(dest.relative_to(BASE))})

        elif self.path.startswith("/load-state"):
            from urllib.parse import urlparse, parse_qs
            qs = parse_qs(urlparse(self.path).query)
            brand = qs.get("brand", [""])[0].replace("..", "").replace("/", "")
            state_file = BASE / brand / "curate" / "state.json"
            if state_file.exists():
                return self.respond(200, __import__("json").loads(state_file.read_text()))
            return self.respond(200, {})

        elif self.path == "/publish-signal":
            length = int(self.headers.get("Content-Length", 0))
            body = json.loads(self.rfile.read(length))
            brand = body.get("brand", "").replace("..", "").replace("/", "")
            # Save full curation state
            state = body.get("state", body)
            dest = BASE / brand / "curate" / "state.json"
            dest.parent.mkdir(parents=True, exist_ok=True)
            dest.write_text(__import__("json").dumps(state, indent=2))
            # Run apply-publish.js immediately — instant publish
            try:
                result = subprocess.run(
                    ["node", "/root/.openclaw/workspace/scripts/revolut/apply-publish.js", brand],
                    capture_output=True, text=True, timeout=30
                )
                output = result.stdout.strip()
                print(f"[publish] {brand}: {output}")
                return self.respond(200, {"ok": True, "brand": brand, "published": True, "output": output})
            except Exception as e:
                print(f"[publish] {brand} ERROR: {e}")
                return self.respond(500, {"ok": False, "brand": brand, "error": str(e)})

        elif self.path == "/apply-slots":
            length = int(self.headers.get("Content-Length", 0))
            body = json.loads(self.rfile.read(length))
            brand = body.get("brand", "").replace("..", "").replace("/", "")
            slots = body.get("slots", {})  # {slot_id: [filename, ...]}
            page = BASE / brand / "index.html"
            if not page.exists():
                return self.respond(404, {"error": "page not found"})
            # Save slots manifest
            manifest = BASE / brand / "curate" / "slots.json"
            manifest.parent.mkdir(parents=True, exist_ok=True)
            manifest.write_text(__import__("json").dumps(slots, indent=2))
            repo = Path("/root/.openclaw/workspace/public")
            subprocess.run(["git", "add", "-A"], cwd=repo)
            subprocess.run(["git", "commit", "-m", f"curate: apply slot assignments for {brand}"], cwd=repo)
            subprocess.run(["git", "push"], cwd=repo)
            return self.respond(200, {"applied": True, "slots": slots})

        elif self.path == "/update-page":
            length = int(self.headers.get("Content-Length", 0))
            body = json.loads(self.rfile.read(length))
            brand = body.get("brand", "").replace("..", "").replace("/", "")
            replacements = body.get("replacements", [])  # [{old_src, new_src}]
            page = BASE / brand / "index.html"
            if not page.exists():
                return self.respond(404, {"error": "page not found"})
            html = page.read_text()
            changed = []
            for r in replacements:
                old = r.get("old_src", "").replace("..", "")
                new = r.get("new_src", "").replace("..", "")
                if old and new and old in html:
                    html = html.replace(old, new)
                    changed.append({"old": old, "new": new})
            page.write_text(html)
            if changed:
                repo = Path("/root/.openclaw/workspace/public")
                subprocess.run(["git", "add", "-A"], cwd=repo)
                subprocess.run(["git", "commit", "-m", f"studio: update {len(changed)} images in {brand}"], cwd=repo)
                subprocess.run(["git", "push"], cwd=repo)
            return self.respond(200, {"updated": changed})

        else:
            return self.respond(404, {"error": "not found"})

if __name__ == "__main__":
    fuser_cmd = f"fuser -k 3099/tcp 2>/dev/null; true"
    os.system(fuser_cmd)
    import time; time.sleep(0.5)
    server = HTTPServer(("0.0.0.0", 3099), Handler)
    print("Gallery API :3099 — delete / upload / save-crop")
    server.serve_forever()
# NOTE: Added update-page handler below
