#!/usr/bin/env python3
"""
critic_run.py — Evaluates next ready_for_critic PNG, routes to Cut/Print/Rejected.
Uses OpenAI Vision API to assess design quality. No agent required.
"""

import json
import os
import shutil
import base64
import urllib.request
import urllib.error
from pathlib import Path

WORKSPACE = Path("/Users/tonyclaw/.openclaw/workspace")
ETSY_DIR = WORKSPACE / "Etsy"
STATE_PATH = ETSY_DIR / "pipeline-state.json"
PIPELINE_TOOL = ETSY_DIR / "tools" / "pipeline_state.py"

DESIGNS_ROOT = Path("/Users/tonyclaw/Documents/Etsy Designs")
CUT_REVIEW = DESIGNS_ROOT / "02-Review-Cut"
PRINT_REVIEW = DESIGNS_ROOT / "02-Review-Print"
REJECTED = DESIGNS_ROOT / "02-Rejected"

AUTH_PROFILES = Path("/Users/tonyclaw/.openclaw/auth-profiles.json")
OPENAI_VISION_URL = "https://api.openai.com/v1/chat/completions"

# Minimum acceptable file size for a valid PNG
MIN_PNG_BYTES = 10_000


def load_openai_key() -> str:
    try:
        profiles = json.loads(AUTH_PROFILES.read_text(encoding="utf-8"))
        for name, profile in profiles.get("profiles", {}).items():
            if "openai" in name.lower() and profile.get("apiKey"):
                return profile["apiKey"]
        return os.environ.get("OPENAI_API_KEY", "")
    except Exception:
        return os.environ.get("OPENAI_API_KEY", "")


def run_pipeline_tool(args: list) -> tuple:
    import subprocess
    r = subprocess.run(
        ["python3", str(PIPELINE_TOOL)] + args,
        check=False, text=True, capture_output=True
    )
    return r.returncode, r.stdout.strip(), r.stderr.strip()


def load_state() -> dict:
    with open(STATE_PATH, "r", encoding="utf-8") as f:
        return json.load(f)


def pick_next_item(state: dict) -> dict | None:
    candidates = [it for it in state.get("items", []) if it.get("status") == "ready_for_critic"]
    if not candidates:
        return None
    candidates.sort(key=lambda x: (-int(x.get("priority", 0)), x.get("created_at", "")))
    return candidates[0]


def ps_set(item_id, stage, status, message, paths=None):
    args = [
        "set", "--path", str(STATE_PATH),
        "--id", item_id,
        "--stage", stage,
        "--status", status,
        "--by", "etsy-critic",
        "--message", message
    ]
    if paths:
        for k, v in paths.items():
            args += ["--path_kv", f"{k}={v}"]
    rc, out, err = run_pipeline_tool(args)
    if rc != 0:
        raise RuntimeError(err or "pipeline_state set failed")


def ps_error(item_id, message):
    run_pipeline_tool([
        "error", "--path", str(STATE_PATH),
        "--id", item_id,
        "--by", "etsy-critic",
        "--message", message
    ])


def evaluate_with_vision(png_path: Path, item: dict, api_key: str) -> dict:
    """
    Use GPT-4o Vision to evaluate the PNG.
    Returns: {"verdict": "cut"|"print"|"reject", "reason": str, "product_types": [...]}
    """
    niche = item.get("niche", "")
    theme = item.get("theme", "")

    # Encode image as base64
    img_bytes = png_path.read_bytes()
    img_b64 = base64.b64encode(img_bytes).decode("utf-8")

    system_prompt = """You are an Etsy SVG design quality evaluator for RoyalStylesCreations.
Evaluate the PNG image and respond ONLY with valid JSON (no markdown, no explanation).

Criteria:
- CUT designs: Bold silhouettes, clean lines, minimal detail, black on white, suitable for Cricut/vinyl cutting. No gradients, no fine text, shapes are connected.
- PRINT designs: High-detail artwork, gradients OK, suitable for sublimation printing on shirts/mugs. Text is OK.
- REJECT: Blurry, nonsensical, contains trademarked characters, unsafe content, or completely wrong for the niche.

Respond with exactly this JSON:
{
  "verdict": "cut" | "print" | "reject",
  "reason": "one sentence",
  "product_types": ["cut"] or ["print"] or ["cut", "print"] or [],
  "quality_score": 1-5
}"""

    user_text = f"Niche: {niche}\nTheme: {theme}\nEvaluate this design:"

    payload = json.dumps({
        "model": "gpt-4o",
        "max_tokens": 200,
        "messages": [
            {"role": "system", "content": system_prompt},
            {
                "role": "user",
                "content": [
                    {"type": "text", "text": user_text},
                    {
                        "type": "image_url",
                        "image_url": {
                            "url": f"data:image/png;base64,{img_b64}",
                            "detail": "low"
                        }
                    }
                ]
            }
        ]
    }).encode("utf-8")

    req = urllib.request.Request(
        OPENAI_VISION_URL,
        data=payload,
        headers={
            "Content-Type": "application/json",
            "Authorization": f"Bearer {api_key}"
        },
        method="POST"
    )

    with urllib.request.urlopen(req, timeout=60) as resp:
        result = json.loads(resp.read().decode("utf-8"))

    raw = result["choices"][0]["message"]["content"].strip()

    # Strip markdown if present
    if raw.startswith("```"):
        raw = raw.split("```")[1]
        if raw.startswith("json"):
            raw = raw[4:]
    raw = raw.strip()

    return json.loads(raw)


def route_design(design_name: str, png_path: Path, verdict: str, evaluation: dict) -> Path:
    """Move design folder to the appropriate review directory."""
    source_folder = png_path.parent

    if verdict == "reject":
        dest_parent = REJECTED
    elif verdict == "print":
        dest_parent = PRINT_REVIEW
    else:  # cut (default)
        dest_parent = CUT_REVIEW

    dest_parent.mkdir(parents=True, exist_ok=True)
    dest = dest_parent / design_name

    # Remove old routing if it exists
    if dest.exists():
        shutil.rmtree(dest)

    shutil.copytree(source_folder, dest)

    # Write design-info.json
    design_info = {
        "design_name": design_name,
        "verdict": verdict,
        "product_types": evaluation.get("product_types", [verdict]),
        "primary_type": verdict,
        "quality_score": evaluation.get("quality_score", 3),
        "reason": evaluation.get("reason", ""),
        "routed_to": str(dest_parent),
    }
    (dest / "design-info.json").write_text(
        json.dumps(design_info, indent=2, ensure_ascii=False),
        encoding="utf-8"
    )

    return dest


def main():
    api_key = load_openai_key()
    if not api_key:
        print("Critic: no OpenAI API key — check auth-profiles.json or OPENAI_API_KEY env")
        return

    state = load_state()
    item = pick_next_item(state)
    if not item:
        print("Critic: no items ready_for_critic")
        return

    item_id = item["id"]
    design_name = item.get("design_name")
    png_path_str = (item.get("paths") or {}).get("png")

    # Claim
    ps_set(item_id, stage="critic_in_review", status="blocked",
           message="Claimed for evaluation (lock held)")

    try:
        if not design_name:
            ps_error(item_id, "Missing design_name")
            return

        if not png_path_str:
            ps_error(item_id, "Missing png path in state")
            return

        png_path = Path(png_path_str)
        if not png_path.exists():
            ps_error(item_id, f"PNG file missing: {png_path}")
            return

        if png_path.stat().st_size < MIN_PNG_BYTES:
            ps_error(item_id, f"PNG too small ({png_path.stat().st_size} bytes) — likely corrupt")
            return

        print(f"Critic: evaluating {design_name} ({png_path.stat().st_size:,} bytes)")

        # Vision evaluation
        evaluation = evaluate_with_vision(png_path, item, api_key)
        verdict = evaluation.get("verdict", "cut")
        reason = evaluation.get("reason", "")
        score = evaluation.get("quality_score", 3)

        print(f"Critic: verdict={verdict}, score={score}, reason={reason}")

        # Route the design
        routed = route_design(design_name, png_path, verdict, evaluation)

        if verdict == "reject":
            ps_set(
                item_id,
                stage="rejected",
                status="complete",
                message=f"Rejected by Critic (score={score}): {reason}",
                paths={"review_folder": str(routed)}
            )
            print(f"Critic rejected: {item_id}")
            return

        # Approved — validate design-info.json was written
        di = routed / "design-info.json"
        if not di.exists():
            ps_error(item_id, f"design-info.json missing after routing: {di}")
            return

        ps_set(
            item_id,
            stage="critic_routed",
            status="ready_for_bundler",
            message=f"Approved ({verdict}, score={score}): {reason}",
            paths={
                "review_folder": str(routed),
                "design_info_json": str(di)
            }
        )
        print(f"Critic routed: {item_id} -> ready_for_bundler ({verdict})")

    except urllib.error.HTTPError as e:
        body = e.read().decode("utf-8", errors="replace")
        ps_error(item_id, f"Vision API HTTP {e.code}: {body[:200]}")
    except json.JSONDecodeError as e:
        ps_error(item_id, f"Vision API returned invalid JSON: {e}")
    except Exception as e:
        ps_error(item_id, f"Unhandled exception: {e}")


if __name__ == "__main__":
    main()
