#!/usr/bin/env python3
"""Quick test script for Qwen MA full body angle parameters.

Tests different h/v/z combos against a full body hero to find
the right settings for low_angle, high_angle, full_body_three_quarter.

Does NOT touch the threepass pipeline. Standalone experimentation only.

Usage:
    python3 test_fullbody_angles.py leviathan/ --character KIAN --hero /path/to/full_body_hero.png
    python3 test_fullbody_angles.py leviathan/ --character KIAN --hero /path/to/full_body_hero.png --angle low_angle
    python3 test_fullbody_angles.py leviathan/ --character KIAN --hero /path/to/full_body_hero.png --custom "h=0,v=-45,z=0"
"""

import argparse
import os
import sys
import time
import urllib.request
from datetime import datetime
from pathlib import Path

# Test configurations: each is (label, h, v, z)
PRESETS = {
    "low_angle": [
        ("low_v-30_z5",   0, -30, 5),   # current default
        ("low_v-30_z0",   0, -30, 0),   # zoomed out — max low
        ("low_v-30_z-2",  0, -30, -2),  # max low, pulled back
        ("low_v-20_z0",   0, -20, 0),   # subtle low
    ],
    "high_angle": [
        ("high_v30_z0",   0,  30, 0),   # current default — max high
        ("high_v30_z5",   0,  30, 5),   # max high, zoomed in
        ("high_v30_z-2",  0,  30, -2),  # max high, pulled back
        ("high_v20_z0",   0,  20, 0),   # subtle high
    ],
    "full_body_three_quarter": [
        ("fb3q_h45_v10",  45, 10, 0),   # current default
        ("fb3q_h45_v0",   45,  0, 0),   # eye level
        ("fb3q_h35_v5",   35,  5, 0),   # less rotation
        ("fb3q_h60_v10",  60, 10, 0),   # more rotation
    ],
    "full_body": [
        ("fb_v10_z0",     0,  10, 0),   # current default
        ("fb_v0_z0",      0,   0, 0),   # dead level
        ("fb_v5_z-2",     0,   5, -2),  # slightly pulled back
    ],
}


def run_qwen_angle(hero_path, output_path, h, v, z, additional_prompt="", negative_prompt=""):
    """Run Qwen MA with specific h/v/z parameters."""
    import fal_client

    image_url = fal_client.upload_file(str(hero_path))

    arguments = {
        "image_urls": [image_url],
        "horizontal_angle": h,
        "vertical_angle": v,
        "zoom": z,
        "lora_scale": 0.9,
        "image_size": "square_hd",
        "num_inference_steps": 40,
        "guidance_scale": 4.5,
        "num_images": 1,
        "enable_safety_checker": False,
    }
    if additional_prompt:
        arguments["additional_prompt"] = additional_prompt
    if negative_prompt:
        arguments["negative_prompt"] = negative_prompt

    t0 = time.time()
    result = fal_client.subscribe(
        "fal-ai/qwen-image-edit-2511-multiple-angles",
        arguments=arguments,
        with_logs=False,
    )
    elapsed = time.time() - t0

    if result and "images" in result and result["images"]:
        img_url = result["images"][0]["url"]
        output_path.parent.mkdir(parents=True, exist_ok=True)
        urllib.request.urlretrieve(img_url, str(output_path))
        return {"success": True, "elapsed": elapsed}
    return {"success": False, "elapsed": elapsed}


def main():
    parser = argparse.ArgumentParser(description="Test Qwen MA full body angle parameters")
    parser.add_argument("project", help="Project path (e.g., leviathan/)")
    parser.add_argument("--character", required=True, help="Character key")
    parser.add_argument("--hero", required=True, help="Full body hero image path")
    parser.add_argument("--angle", help="Test a specific angle preset (low_angle, high_angle, full_body_three_quarter, full_body). Default: all")
    parser.add_argument("--custom", help="Custom params: h=0,v=-45,z=0")
    parser.add_argument("--no-prompt", action="store_true", help="Run without additional_prompt (raw rotation only)")
    parser.add_argument("--dry-run", action="store_true", help="Show what would run")
    args = parser.parse_args()

    hero_path = Path(args.hero).resolve()
    if not hero_path.is_file():
        print(f"ERROR: Hero not found: {hero_path}", file=sys.stderr)
        sys.exit(1)

    project_path = Path(args.project).resolve()
    char_upper = args.character.upper()
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    out_base = project_path / "visual" / "lora_candidates" / char_upper / "shootout" / f"fullbody_test_{timestamp}"

    # Build test list
    tests = []
    if args.custom:
        # Parse h=0,v=-45,z=0
        params = {}
        for part in args.custom.split(","):
            k, v = part.strip().split("=")
            params[k.strip()] = int(v.strip())
        label = f"custom_h{params.get('h',0)}_v{params.get('v',0)}_z{params.get('z',0)}"
        tests.append((label, params.get("h", 0), params.get("v", 0), params.get("z", 0)))
    elif args.angle:
        if args.angle in PRESETS:
            tests = PRESETS[args.angle]
        else:
            print(f"ERROR: Unknown angle '{args.angle}'. Available: {', '.join(PRESETS.keys())}", file=sys.stderr)
            sys.exit(1)
    else:
        # All presets
        for angle_name, preset_list in PRESETS.items():
            tests.extend(preset_list)

    # Prompts (same as threepass)
    if args.no_prompt:
        additional_prompt = ""
        negative_prompt = ""
    else:
        additional_prompt = (
            "Photorealistic skin with visible pores, fine lines, and natural imperfections. "
            "Subtle skin texture variation — uneven tone, minor blemishes, faint scars. "
            "No airbrushing. No plastic skin. No smoothing. Raw, editorial photography look. "
            "Visible pore detail on nose, cheeks, and forehead. Natural subsurface scattering."
            " Full body photograph showing complete figure from head to feet. "
            "Anatomically correct adult human proportions: head is 1/7.5 of total body height. "
            "Shoulders wider than the head. Torso approximately 3 head-heights long. "
            "Legs approximately 3.5-4 head-heights long. Arms reach mid-thigh. "
            "Do NOT enlarge the head relative to the body."
        )
        negative_prompt = (
            "smooth plastic skin, airbrushed, wax figure, porcelain, doll-like, "
            "CGI render, video game character, overly clean skin, beauty filter"
            ", disproportionate head, oversized head, big head, bobblehead, chibi, "
            "short legs, stubby limbs, extra arms, three arms, extra limbs, "
            "mutated hands, deformed body, dwarfism proportions"
        )

    cost_per = 0.036
    print(f"\n{'='*60}")
    print(f"FULL BODY ANGLE TEST — {char_upper}")
    print(f"{'='*60}")
    print(f"  Hero:      {hero_path.name}")
    print(f"  Tests:     {len(tests)}")
    print(f"  Prompts:   {'OFF (raw rotation)' if args.no_prompt else 'ON (skin+proportionality)'}")
    print(f"  Output:    {out_base}")
    print(f"  Est. cost: ~${len(tests) * cost_per:.2f}")
    print(f"{'='*60}")
    for label, h, v, z in tests:
        print(f"  {label:30s}  h={h:4d}  v={v:4d}  z={z:2d}")
    print()

    if args.dry_run:
        print("DRY RUN — nothing generated.")
        sys.exit(0)

    if not os.environ.get("FAL_KEY"):
        print("ERROR: FAL_KEY not set", file=sys.stderr)
        sys.exit(1)

    out_base.mkdir(parents=True, exist_ok=True)
    results = []

    for i, (label, h, v, z) in enumerate(tests):
        out_file = out_base / f"{label}.png"
        print(f"  [{i+1}/{len(tests)}] {label} (h={h}, v={v}, z={z})...", end=" ", flush=True)

        try:
            r = run_qwen_angle(hero_path, out_file, h, v, z,
                               additional_prompt=additional_prompt,
                               negative_prompt=negative_prompt)
            if r["success"]:
                print(f"OK ({r['elapsed']:.1f}s)")
                results.append((label, out_file))
            else:
                print("FAILED")
        except Exception as e:
            print(f"ERROR: {str(e)[:80]}")

        if i < len(tests) - 1:
            time.sleep(1)

    print(f"\n{'='*60}")
    print(f"COMPLETE: {len(results)}/{len(tests)} succeeded")
    print(f"  Output: {out_base}")
    print(f"{'='*60}")

    # Open all results
    if results:
        import subprocess
        files = [str(f) for _, f in results]
        subprocess.run(["open"] + files)


if __name__ == "__main__":
    main()
