#!/usr/bin/env python3
"""
Automated Anime Consistency Workflow - Local PC (ABS) Version
- Uses local ComfyUI installation on gaming PC
- Analyzes frames against references using perceptual hash + SSIM
- Iteratively improves prompts until 85% consistency is reached
"""

import os
import sys
import json
import time
import argparse
import requests
from pathlib import Path
from datetime import datetime
from PIL import Image
import imagehash
import cv2
import numpy as np
from skimage.metrics import structural_similarity as ssim
from skimage.color import rgb2gray

# Local PC ComfyUI settings
COMFYUI_URL = "http://localhost:8188"

# Auto-detect paths based on OS
import platform
if platform.system() == "Windows":
    LOCAL_OUTPUT_DIR = "C:/AI/ComfyUI/output"
    REFERENCES_DIR = "C:/Users/fbmor/broken-spire-comparison/references"
else:
    # WSL or Linux - use /mnt/c paths for Windows mounts
    LOCAL_OUTPUT_DIR = "/mnt/c/AI/ComfyUI/output"
    REFERENCES_DIR = "/mnt/c/Users/fbmor/broken-spire-comparison/references"

FRAME_MAPPING = {
    "02_ash_birth": "Violet humaine",
    "03_far_future_ash_evil": "Far-Future Ash",
    "05_everly_soldier": "Everly",
    "06_eva_doctor": "Éva Moreau",
    "07_nova_warrior": "Nova Human",
    "08_violet_devil": "Violet Devil",
    "09_lin_weishan": "Lin Weishan",
    "10_tc23_esper": "TC-23",
    "11_jonas_ghost": "Jonas",
    "13_ash_everly_tension": "Ash",
    "14_ash_eva_medical": "Ash",
    "15_ash_nova_war": "Ash",
    "16_far_future_ash_watching": "Far-Future Ash",
    "17_all_characters_triangle": "Ash",
    "19_ash_walking_hope": "Ash",
}

ART_STYLE = "dark gothic fantasy illustration in the style of Castlevania anime, Ayami Kojima–inspired, tall slender vampire hunter with flowing coat and ornate leather armor, long hair blowing in the wind, standing on a castle balcony at night, full moon and storm clouds, baroque architecture, intricate metal and fabric details, dramatic chiaroscuro lighting, rich muted reds and golds, painterly textures, realistic anime proportions, highly detailed, 4k"

CHARACTER_CONFIG = {
    "Ash": {"seed": 42, "age": "21"},
    "Ash 2": {"seed": 43, "age": "18"},
    "Far-Future Ash": {"seed": 100, "age": "1000000"},
    "Nova Human": {"seed": 200, "age": "25"},
    "Nova Devil": {"seed": 201, "age": "175"},
    "Éva Moreau": {"seed": 300, "age": "25"},
    "Everly": {"seed": 400, "age": "25"},
    "Lin Weishan": {"seed": 500, "age": "70"},
    "TC-23": {"seed": 600, "age": "100000"},
    "Jonas": {"seed": 700, "age": "28"},
    "Violet humaine": {"seed": 800, "age": "18"},
    "Violet Devil": {"seed": 801, "age": "80"},
    "Esper": {"seed": 900, "age": "250"},
    "TK": {"seed": 1000, "age": "75"},
    "Seraphine Vale": {"seed": 1100, "age": "55"},
}

CHARACTER_PROFILES = {
    "Ash": {
        "ref": "Ash.png",
        "base_prompt": "masterpiece, best quality, very aesthetic, absurdres, 1boy, MALE, man, solo, 21 years old, young adult, blond hair, medium length, messy, falling across forehead, pale skin, slim athletic body, dancer build, large expressive anime eyes, blue eyes, strong cheekbones, soft androgynous face, black tribal tattoos on arm and shoulder, sharp organic claw flame pattern tattoos, detailed face, detailed eyes, looking at viewer, dark background, dramatic lighting",
        "negative": "lowres, bad anatomy, bad hands, text, error, cropped, worst quality, low quality, blurry, artist name, FEMALE BODY, female anatomy, breasts, feminine face, WRONG GENDER, girl, woman, female"
    },
    "Ash 2": {
        "ref": "Ash 2.png",
        "base_prompt": "masterpiece, best quality, very aesthetic, absurdres, 1boy, MALE, man, solo, 18 years old, young looking, blond hair, medium length, messy, falling across forehead, pale skin, slim athletic body, dancer build, large expressive anime eyes, blue eyes, strong cheekbones, soft androgynous face, black tribal tattoos on arm and shoulder, sharp organic claw flame pattern tattoos, detailed face, detailed eyes, looking at viewer, dark background, dramatic lighting",
        "negative": "lowres, bad anatomy, bad hands, text, error, cropped, worst quality, low quality, blurry, artist name, FEMALE BODY, female anatomy, breasts, feminine face, WRONG GENDER, girl, woman, female"
    },
    "Far-Future Ash": {
        "ref": "Far-Future Ash.png",
        "base_prompt": "masterpiece, best quality, very aesthetic, absurdres, 1man, MALE, man, solo, ancient being, timeless, god of death, million years old, black knight, massive dark armored suit, black hole core in chest glowing, glowing red eyes from helmet slit, terrifying, apocalyptic, destroyed city background, event horizon, dark energy swirling, multiple weapons, muscular, tall, ominous, horror, villain, evil, intense expression, menacing",
        "negative": "lowres, bad anatomy, bad hands, text, error, cropped, worst quality, low quality, blurry, artist name, MALE BODY, male anatomy, muscularmasculine, masculine face, WRONG GENDER, boy, man, male"
    },
    "Everly": {
        "ref": "Everly.png",
        "base_prompt": "masterpiece, best quality, very aesthetic, absurdres, 1girl, FEMALE, woman, solo, 25 years old, young adult, short dark hair, sharp gothic make-up, intense eyes, dark dangerous expression, fit athletic body, military uniform, tactical gear, multiple weapons, scars visible, confident pose, hand on weapon, cyberpunk, dark sci-fi, detailed face, detailed eyes, mature appearance",
        "negative": "lowres, bad anatomy, bad hands, text, error, cropped, worst quality, low quality, blurry, artist name, MALE BODY, male anatomy, muscularmasculine, masculine face, WRONG GENDER, boy, man, male"
    },
    "Éva Moreau": {
        "ref": "��va Moreau.png",
        "base_prompt": "masterpiece, best quality, very aesthetic, absurdres, 1girl, FEMALE, woman, solo, 25 years old, young adult, french woman, professional, average build, shoulder length brown hair, BROWN EYES, tanned skin, softer features, intelligent eyes, focused expression, lab coat, medical clothing, surgical gloves, LARGER BREASTS, sterile environment, blue medical lights, detailed face, cyberpunk clinic, dramatic lighting, cinematic",
        "negative": "lowres, bad anatomy, bad hands, text, error, cropped, worst quality, low quality, blurry, artist name, MALE BODY, male anatomy, muscularmasculine, masculine face, WRONG GENDER, boy, man, male"
    },
    "Nova Human": {
        "ref": "Nova Human.png",
        "base_prompt": "masterpiece, best quality, very aesthetic, absurdres, 1girl, FEMALE, woman, solo, 25 years old, young adult, fighter build, BLOND LONG HAIR flowing, YAKUZA STYLE TATTOOS visible, fierce expression, GOLD EYES, playful smile, combat gear, multiple stylish weapons, dynamic pose, action pose, battle stance, glowing energy weapons, dramatic lighting, cinematic, anime action, intense, stylish, war scene, detailed face, detailed eyes",
        "negative": "lowres, bad anatomy, bad hands, text, error, cropped, worst quality, low quality, blurry, artist name, MALE BODY, male anatomy, muscularmasculine, masculine face, WRONG GENDER, boy, man, male"
    },
    "Nova Devil": {
        "ref": "Nova devil.png",
        "base_prompt": "masterpiece, best quality, very aesthetic, absurdres, 1girl, FEMALE, woman, solo, 175 years old, ancient, RED DEVIL form, huge breasts, large hips, MOSTLY NAKED, elegant, red hair, red devil horns, glowing red eyes, dark wings, elegant dangerous pose, hand with claws, beautiful and terrifying, dark aura, crimson energy, night background, dramatic lighting, horror, beautiful, duality, dark fantasy, detailed face",
        "negative": "lowres, bad anatomy, bad hands, text, error, cropped, worst quality, low quality, blurry, artist name, MALE BODY, male anatomy, muscularmasculine, masculine face, WRONG GENDER, boy, man, male"
    },
    "Violet humaine": {
        "ref": "Violet humaine.png",
        "base_prompt": "masterpiece, best quality, very aesthetic, absurdres, 1girl, FEMALE, woman, solo, 18 years old, young looking, orphan, HUMAN FORM not devil, CRUMPY DIRTY CLOTHES, not sci-fi, messy unwashed look, purple hair, tired desperate expression, detailed face, post apocalyptic, detailed eyes, looking at viewer",
        "negative": "lowres, bad anatomy, bad hands, text, error, cropped, worst quality, low quality, blurry, artist name, MALE BODY, male anatomy, muscularmasculine, masculine face, WRONG GENDER, boy, man, male"
    },
    "Violet Devil": {
        "ref": "Violet Devil.png",
        "base_prompt": "masterpiece, best quality, very aesthetic, absurdres, 1girl, FEMALE, woman, solo, 80 years old, ancient, RED DEVIL form, huge breasts, large hips, MOSTLY NAKED, elegant, purple hair, red devil horns, glowing red eyes, dark wings, elegant dangerous pose, hand with claws, beautiful and terrifying, dark aura, crimson energy, night background, dramatic lighting, horror, beautiful, duality, dark fantasy, detailed face",
        "negative": "lowres, bad anatomy, bad hands, text, error, cropped, worst quality, low quality, blurry, artist name, MALE BODY, male anatomy, muscularmasculine, masculine face, WRONG GENDER, boy, man, male"
    },
    "Lin Weishan": {
        "ref": "Lin Weishan.png",
        "base_prompt": "masterpiece, best quality, very aesthetic, absurdres, 1girl, FEMALE, woman, solo, 70 years old, elderly, asian woman, fighter athletic build, CULT TATTOOS visible, martial arts pose, fighter monk and cultist mix, POST APOCALYPTIC CLOTHING, determined expression, messy wild look, detailed face, action, intense, dramatic lighting",
        "negative": "lowres, bad anatomy, bad hands, text, error, cropped, worst quality, low quality, blurry, artist name, MALE BODY, male anatomy, muscularmasculine, masculine face, WRONG GENDER, boy, man, male"
    },
    "TC-23": {
        "ref": "TC-23.png",
        "base_prompt": "masterpiece, best quality, very aesthetic, absurdres, 1girl, FEMALE, woman, solo, 100000 years old, ancient esper, immortal, BLUEISH SKIN synthetic, LESS ARMORED, WHITE SYNTHETIC SKIN visible, mechanical augmented body, cybernetic augments visible, glowing cybernetic eyes, esper powers, glowing energy surrounding, futuristic outfit, robot parts, detailed machinery, dramatic lighting, sci-fi, cyberpunk, esper, mechanical, detailed face",
        "negative": "lowres, bad anatomy, bad hands, text, error, cropped, worst quality, low quality, blurry, artist name, MALE BODY, male anatomy, muscularmasculine, masculine face, WRONG GENDER, boy, man, male"
    },
    "Jonas": {
        "ref": "Jonas.png",
        "base_prompt": "masterpiece, best quality, very aesthetic, absurdres, 1man, MALE, man, solo, 28 years old, young adult, large muscular build, imposing, scarred face, short brown hair, warm smile, kind eyes, weathered skin, military combat gear, CYBORG LEFT EAR visible, CYBERNETIC NECK partial, detailed face, dramatic lighting, cinematic",
        "negative": "lowres, bad anatomy, bad hands, text, error, cropped, worst quality, low quality, blurry, artist name, FEMALE BODY, female anatomy, breasts, feminine face, WRONG GENDER, girl, woman, female"
    },
    "Esper": {
        "ref": "TC-23.png",
        "base_prompt": "masterpiece, best quality, very aesthetic, absurdres, 1girl, FEMALE, woman, solo, 250 years old, ancient esper, BLUEISH SKIN synthetic, LESS ARMORED, WHITE SYNTHETIC SKIN visible, mechanical augmented body, cybernetic augments visible, glowing cybernetic eyes, esper powers, glowing energy surrounding, futuristic outfit, robot parts, detailed machinery, dramatic lighting, sci-fi, cyberpunk, esper, mechanical, detailed face",
        "negative": "lowres, bad anatomy, bad hands, text, error, cropped, worst quality, low quality, blurry, artist name, MALE BODY, male anatomy, muscularmasculine, masculine face, WRONG GENDER, boy, man, male"
    },
    "TK": {
        "ref": "TC-23.png",
        "base_prompt": "masterpiece, best quality, very aesthetic, absurdres, 1girl, FEMALE, woman, solo, 75 years old, elderly, BLUEISH SKIN synthetic, LESS ARMORED, WHITE SYNTHETIC SKIN visible, mechanical augmented body, cybernetic augments visible, glowing cybernetic eyes, esper powers, glowing energy surrounding, futuristic outfit, robot parts, detailed machinery, dramatic lighting, sci-fi, cyberpunk, esper, mechanical, detailed face",
        "negative": "lowres, bad anatomy, bad hands, text, error, cropped, worst quality, low quality, blurry, artist name, MALE BODY, male anatomy, muscularmasculine, masculine face, WRONG GENDER, boy, man, male"
    },
    "Seraphine Vale": {
        "ref": "Everly.png",
        "base_prompt": "masterpiece, best quality, very aesthetic, absurdres, 1girl, FEMALE, woman, solo, 55 years old, mature, short dark hair, sharp gothic make-up, intense eyes, dark dangerous expression, fit athletic body, military uniform, tactical gear, multiple weapons, scars visible, confident pose, hand on weapon, cyberpunk, dark sci-fi, detailed face, detailed eyes",
        "negative": "lowres, bad anatomy, bad hands, text, error, cropped, worst quality, low quality, blurry, artist name, MALE BODY, male anatomy, muscularmasculine, masculine face, WRONG GENDER, boy, man, male"
    }
}

CHECKPOINT_NAME = "sd_xl_anime_final.safetensors"
CONSISTENCY_THRESHOLD = 0.85
MAX_ITERATIONS = 10

def calculate_consistency_score(gen_path, ref_path):
    try:
        if not os.path.exists(gen_path) or not os.path.exists(ref_path):
            return 0.0
        
        h1 = imagehash.phash(Image.open(gen_path))
        h2 = imagehash.phash(Image.open(ref_path))
        hash_sim = 1 - (h1 - h2) / len(h1.hash) ** 2
        
        img1 = cv2.imread(gen_path)
        img2 = cv2.imread(ref_path)
        if img1 is None or img2 is None:
            return 0.0
        
        img1 = cv2.resize(img1, (256, 256))
        img2 = cv2.resize(img2, (256, 256))
        gray1 = rgb2gray(cv2.cvtColor(img1, cv2.COLOR_BGR2RGB))
        gray2 = rgb2gray(cv2.cvtColor(img2, cv2.COLOR_BGR2RGB))
        ssim_score = ssim(gray1, gray2, data_range=1.0)
        
        return hash_sim * 0.6 + ssim_score * 0.4
    except Exception as e:
        print(f"Error calculating score: {e}")
        return 0.0

def find_generated_file(output_dir, frame_prefix):
    import glob
    pattern = os.path.join(output_dir, f"{frame_prefix}*.png")
    files = glob.glob(pattern)
    if not files:
        return None
    files.sort(key=os.path.getmtime, reverse=True)
    return os.path.basename(files[0])

def analyze_frame(frame_prefix, char_name):
    if char_name not in CHARACTER_PROFILES:
        return {"score": 1.0, "passed": True}
    
    profile = CHARACTER_PROFILES[char_name]
    ref_path = os.path.join(REFERENCES_DIR, profile["ref"])
    
    if not os.path.exists(ref_path):
        return {"score": 0.0, "passed": False, "reason": "Reference not found"}
    
    gen_file = find_generated_file(LOCAL_OUTPUT_DIR, frame_prefix)
    if not gen_file:
        return {"score": 0.0, "passed": False, "reason": "No generated file"}
    
    gen_path = os.path.join(LOCAL_OUTPUT_DIR, gen_file)
    score = calculate_consistency_score(gen_path, ref_path)
    
    return {"score": score, "passed": score >= CONSISTENCY_THRESHOLD, "gen_file": gen_file}

def queue_generation(prompt, negative_prompt, filename_prefix, seed=None):
    if seed is None:
        seed = int(time.time() % 100000)
    payload = {
        "prompt": {
            "1": {"class_type": "CheckpointLoaderSimple", "inputs": {"ckpt_name": CHECKPOINT_NAME}},
            "2": {"class_type": "CLIPTextEncode", "inputs": {"text": prompt, "clip": ["1", 1]}},
            "3": {"class_type": "CLIPTextEncode", "inputs": {"text": negative_prompt, "clip": ["1", 1]}},
            "4": {"class_type": "EmptyLatentImage", "inputs": {"width": 832, "height": 1216, "batch_size": 1}},
            "5": {"class_type": "KSampler", "inputs": {
                "seed": seed, "steps": 30, "cfg": 7, 
                "sampler_name": "euler_ancestral", "scheduler": "normal", "denoise": 1.0,
                "model": ["1", 0], "positive": ["2", 0], "negative": ["3", 0], "latent_image": ["4", 0]
            }},
            "6": {"class_type": "VAEDecode", "inputs": {"samples": ["5", 0], "vae": ["1", 2]}},
            "7": {"class_type": "SaveImage", "inputs": {"filename_prefix": filename_prefix, "images": ["6", 0]}}
        }
    }
    
    try:
        resp = requests.post(f"{COMFYUI_URL}/api/prompt", json=payload, timeout=30)
        return resp.json()
    except Exception as e:
        print(f"Error: {e}")
        return {"error": str(e)}

def regenerate_frame(frame_prefix, char_name, iteration):
    if char_name not in CHARACTER_PROFILES:
        return None
    
    profile = CHARACTER_PROFILES[char_name]
    ref_filename = profile.get("ref", "Ash.png")
    
    seed = CHARACTER_CONFIG.get(char_name, {}).get("seed", 42)
    
    enhanced_prompt = f"{ART_STYLE}, {profile['base_prompt']}, highest quality, most accurate character match"
    enhanced_negative = f"{profile['negative']}, low quality, inaccurate, different character style, blurry, distorted"
    
    filename_prefix = f"{frame_prefix}_iter{iteration}"
    print(f"  Generating: {filename_prefix}")
    print(f"    Reference: {ref_filename}")
    print(f"    Seed: {seed}")
    
    result = queue_generation(enhanced_prompt, enhanced_negative, filename_prefix, seed)
    
    if 'prompt_id' in result:
        time.sleep(5)
        return result['prompt_id']
    else:
        print(f"  Error: {result}")
    return None

def wait_for_queue():
    print("  Waiting for queue...")
    while True:
        try:
            resp = requests.get(f"{COMFYUI_URL}/api/queue", timeout=10)
            q = resp.json()
            running = len(q.get('queue_running', []))
            pending = len(q.get('queue_pending', []))
            if not running and not pending:
                break
            print(f"    Queue: {running} running, {pending} pending...")
            time.sleep(5)
        except:
            break

def check_comfyui():
    try:
        resp = requests.get(f"{COMFYUI_URL}/system_stats", timeout=5)
        return resp.status_code == 200
    except:
        return False

def main():
    global CONSISTENCY_THRESHOLD, MAX_ITERATIONS
    
    # Auto-start ComfyUI if not running
    print("Checking ComfyUI...")
    for attempt in range(3):
        if check_comfyui():
            break
        print(f"  ComfyUI not running, attempt {attempt+1}/3...")
        import subprocess
        subprocess.Popen([
            "python3", "main.py", "--listen", "0.0.0.0", "--port", "8188", "--lowvram"
        ], cwd="/mnt/c/Users/fbmor/ComfyUI", stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
        for w in range(20):
            time.sleep(1)
            if check_comfyui():
                break
    
    print("="*60)
    print("AUTOMATED ANIME CONSISTENCY - LOCAL PC (ABS)")
    print(f"ComfyUI: {COMFYUI_URL}")
    print(f"Output: {LOCAL_OUTPUT_DIR}")
    print(f"Threshold: {CONSISTENCY_THRESHOLD*100}%")
    print("="*60)
    
    if not check_comfyui():
        print(f"❌ ComfyUI not accessible at {COMFYUI_URL}")
        return
    
    parser = argparse.ArgumentParser()
    parser.add_argument("--analyze", action="store_true")
    parser.add_argument("--iterative", action="store_true")
    parser.add_argument("--threshold", type=float, default=0.85)
    parser.add_argument("--max-iterations", type=int, default=10)
    parser.add_argument("--generate", type=str, help="Generate a single character (e.g., --generate Ash)")
    args = parser.parse_args()
    
    CONSISTENCY_THRESHOLD = args.threshold
    MAX_ITERATIONS = args.max_iterations
    
    if args.analyze:
        results = []
        for frame_prefix, char_name in FRAME_MAPPING.items():
            result = analyze_frame(frame_prefix, char_name)
            result["frame"] = frame_prefix
            result["character"] = char_name
            results.append(result)
            
            status = "✅" if result["passed"] else "❌"
            score = result.get("score", 0)
            print(f"{status} {frame_prefix}: {score:.1%}")
        
        passed = sum(1 for r in results if r["passed"])
        print(f"\nPassed: {passed}/{len(results)} ({passed/len(results)*100:.1f}%)")
    
    elif args.iterative:
        for iteration in range(1, MAX_ITERATIONS + 1):
            print(f"\n{'='*50}")
            print(f"ITERATION {iteration}/{MAX_ITERATIONS}")
            print(f"{'='*50}")
            
            results = []
            for frame_prefix, char_name in FRAME_MAPPING.items():
                result = analyze_frame(frame_prefix, char_name)
                result["frame"] = frame_prefix
                result["character"] = char_name
                results.append(result)
            
            passed = [r for r in results if r["passed"]]
            failing = [r for r in results if not r["passed"]]
            
            print(f"\nProgress: {len(passed)}/{len(results)} passed ({len(passed)/len(results)*100:.1f}%)")
            
            for r in results:
                status = "✅" if r["passed"] else "❌"
                score = r.get("score", 0)
                print(f"  {status} {r['frame']}: {score:.1%}")
            
            if not failing:
                print(f"\n🎉 ALL PASSED!")
                break
            
            print(f"\nRegenerating {len(failing)} frames...")
            
            for r in failing:
                regenerate_frame(r["frame"], r["character"], iteration)
            
            wait_for_queue()
        
        print(f"\n{'='*50}")
        print("DONE - stopping pod")
        print(f"{'='*50}")

    if args.generate:
        char = args.generate
        if char not in CHARACTER_PROFILES:
            print(f"Unknown character: {char}")
            print(f"Available: {list(CHARACTER_PROFILES.keys())}")
            return
        profile = CHARACTER_PROFILES[char]
        ref_filename = profile["ref"]
        seed = CHARACTER_CONFIG.get(char, {}).get("seed", 42)
        age = CHARACTER_CONFIG.get(char, {}).get("age", "")
        
        filename_prefix = f"{char}_{age}yo" if age else char
        
        print(f"Generating {char} (age: {age}, seed: {seed})")
        print(f"  Reference: {ref_filename}")
        print(f"  Filename: {filename_prefix}")
        
        prompt = profile['base_prompt']
        negative = profile['negative']
        
        result = queue_generation(prompt, negative, filename_prefix, seed=seed)
        
        if 'prompt_id' in result:
            print(f"  ✅ Queued: {result['prompt_id']}")
        else:
            print(f"  ❌ Error: {result}")
        return

if __name__ == "__main__":
    main()