"""
Script específico para generar el lote estilo carrusel (4 etapas: sugerente, spicy1, spicy2, explícito1)
para Shoko Komi y Rias Gremory utilizando sus OUTFITS BASE canónicos.
Basado en la arquitectura oficial de pack_builder.py y api_client.py.
"""

import sys
import os
import random
from pathlib import Path

# Forzar codificación UTF-8 en consola
if sys.stdout.encoding.lower() != 'utf-8':
    sys.stdout.reconfigure(encoding='utf-8')

# Añadir directorio raíz al path
ROOT_DIR = Path(__file__).resolve().parent.parent
sys.path.append(str(ROOT_DIR))

import Engine.pose_bank as pbank
import Engine.pack_builder as pb
from Engine.prompt_builder import PromptBuilder
from Engine.api_client import ForgeClient

# Definición exacta de los personajes con sus Outfits Base
CUSTOM_CHARACTERS = {
    "Komi": {
        "key": "Shouko_Komi_Komi_Cant_Communicate_-_Illustrious_V2",
        "line": "<lora:Shouko_Komi_Komi_Cant_Communicate_-_Illustrious_V2:0.8> sh0uk0, 1girl, solo, dark purple hair, long hair, purple eyes, medium breasts, school uniform, jacket, blue jacket, striped clothes, striped skirt, pleated skirt, red skirt, blazer, black pantyhose, long sleeves, bow, white shirt"
    },
    "Rias": {
        "key": "rias gremory",
        "line": "rias gremory, high school dxd, 1girl, solo, blue eyes, hair between eyes, red hair, very long hair, ahoge, huge ahoge, large breasts, black capelet, striped shirt, white shirt, neck ribbon, black ribbon, long sleeves, black corset, buttons, pleated skirt, frilled skirt, purple skirt"
    },
    "Asuna_Bunny": {
        "key": "Hoseki_BlueArchive_AsunaIchinose_IllustriousXL_v2",
        "line": "<lora:Hoseki_BlueArchive_AsunaIchinose_IllustriousXL_v2:1> 1chn, large breasts, blue halo, blue eyes, blonde hair, hair over one eye, very long hair, hair ribbon, blue ribbon,white hairband, fake animal ears, playboy bunny, blue leotard, highleg leotard, detached collar, blue traditional bowtie, white elbow gloves, thighband pantyhose, black pantyhose, white shoes, high heels"
    },
    "Nicole_Demara": {
        "key": "Hoseki_ZenlessZoneZero_NicoleDemara_IllustriousXL_v1",
        "line": "<lora:Hoseki_ZenlessZoneZero_NicoleDemara_IllustriousXL_v1:0.8> n1c0le, large breasts, green eyes, multicolored nails, pink hair, asymmetrical bangs, long hair, two side up, hair ribbon, black ribbon, hairclip, yellow heart earrings, heart necklace, black necklace,heart choker, black choker, two-tone jacket, black jacket, white jacket, shoulder cutout, detached sleeves, two-tone tube top, white tube top, black tube top, cleavage, midriff, o-ring belt, open belt, black shorts, fur-trimmed shorts, micro shorts, thigh belt, single thighhigh, black thighhighs, torn thighhighs, ankle strap, single sock, black socks"
    }
}

def generate_carousel_batch(char_name: str, dry_run: bool = False):
    print(f"\n==================================================")
    print(f" INICIANDO LOTE CARRUSEL (4 ETAPAS) PARA: {char_name}")
    print(f"==================================================")
    
    if char_name not in CUSTOM_CHARACTERS:
        print(f"[!] Personaje '{char_name}' no encontrado en el diccionario custom.")
        return

    char_data = CUSTOM_CHARACTERS[char_name]
    char_line = char_data["line"]
    
    # Inicializar componentes del motor
    pbuilder = PromptBuilder(ROOT_DIR)
    bank = pbank.load_bank()
    avoid = pbank.load_avoid()
    pools = {lvl: pb.load_pool_lines(lvl) for lvl in ['sugerente', 'spicy', 'explicito']}
    cameras = pb.load_cameras()
    
    # Configurar generador aleatorio con seed reproducible
    rng = random.Random(20261102)
    
    # Componer 4 jobs usando la estructura de stages oficial (1 por etapa)
    # Usamos un tema neutro o base para iluminación/escena sin alterar el outfit base
    stage_quotas = {'sugerente': 1, 'spicy1': 1, 'spicy2': 1, 'explicit1': 1}
    
    jobs = pb.compose_theme_jobs(
        theme_names=['oficina'], # Tema de apoyo para iluminación/escena de fondo
        target=4,
        quotas=None,
        variation='random',
        variants={},
        dress='auto',
        explore=False,
        bank=bank,
        avoid=avoid,
        pools=pools,
        cameras=cameras,
        rng=rng,
        allow_legacy_outfit=True,
        stage_quotas=stage_quotas
    )
    
    print(f"-> Jobs de etapas compuestos: {len(jobs)}")
    
    # Directorio de salida
    out_dir = ROOT_DIR / "Output" / "carousel_tests" / char_name
    out_dir.mkdir(parents=True, exist_ok=True)
    
    forge = ForgeClient() if not dry_run else None
    
    records = []
    for i, j in enumerate(jobs):
        # Sustituimos la línea de outfit del tema por nuestro OUTFIT BASE canónico
        # Extraemos la escena y la iluminación del job compuesto
        scene_part = getattr(j, 'scene_line', 'indoors')
        light_part = getattr(j, 'light_line', 'soft lighting')
        
        # Ensamblaje oficial siguiendo DOCUMENTACION/02-estructura-prompt.md
        # 1. Tramo Master + LoRAs
        master_part = pbuilder.master_prompt
        
        # 2. Tramo Personaje + Outfit Base (limpiando complementos heredados no deseados como gafas)
        char_cleaned = char_data["line"]
        if j.stage in ('explicit1', 'explicit2', 'spicy2'):
            char_cleaned = char_cleaned.replace(', solo', '').replace('solo,', '').replace('solo', '')
            
        char_tramo = f"1girl, {char_cleaned}"
        
        # 3. Tramo del Hombre (solo si la pose del banco requiere interacción de pareja / acto explícito con dos personas)
        boy_tramo = None
        # Verificamos si la pose tiene tags explícitos de pareja o interacción con hombre (ej. sex, penetration, cowgirl, missionary, etc.)
        pose_lower = j.pose_line.lower()
        couple_tags = ['sex', 'penetration', 'intercourse', 'cowgirl position', 'missionary', 'doggystyle', 'paizuri', 'fellatio', 'pussy insertion', 'after_sex']
        if any(tag in pose_lower for tag in couple_tags) and j.stage in ('explicit1', 'explicit2'):
            boy_tramo = "1boy, (faceless male:1.4), muscular body, broad shoulders, taller male"
            
        # 4. Tramo de Pose / Escena con subgrupos y ángulo al final
        pose_tramo = f"{scene_part}, {light_part}, {j.pose_line}"
        
        # Unimos con BREAK según la estructura oficial
        tramos = [master_part, char_tramo]
        if boy_tramo:
            tramos.append(boy_tramo)
        tramos.append(pose_tramo)
        
        full_prompt = "\nBREAK\n".join(tramos)
        neg_prompt = pbuilder.negative_prompt
        
        print(f"\n[Etapa {i+1}: {j.stage.upper()}] (Pose ID: {j.pose_id})")
        print(f"Prompt: {full_prompt}")
        
        if dry_run:
            print("[DRY-RUN] Omitiendo llamada a Forge.")
            continue
            
        # Llamada real usando ForgeClient oficial
        try:
            print("-> Enviando a Forge Neo...")
            images, info = forge.txt2img(
                prompt=full_prompt,
                negative_prompt=neg_prompt,
                width=832,
                height=1216,
                steps=28,
                cfg_scale=6.0,
                sampler_name="DPM++ 2M Karras",
                seed=int(rng.randint(100000, 99999999)),
                clip_skip=2
            )
            if images:
                file_name = f"{i+1:02d}_{j.stage}_{j.pose_id}.png"
                file_path = out_dir / file_name
                file_path.write_bytes(images[0])
                print(f"✓ Guardado en: {file_path}")
                records.append({
                    "stage": j.stage,
                    "pose_id": j.pose_id,
                    "file": file_name,
                    "prompt": full_prompt
                })
        except Exception as e:
            print(f"❌ Error generando imagen en stage {j.stage}: {e}")
            
    if not dry_run and records:
        import json
        manifest_path = out_dir / "manifest.json"
        manifest_path.write_text(json.dumps(records, indent=2, ensure_ascii=False), encoding='utf-8')
        print(f"\n✨ Lote de {char_name} completado. Manifiesto guardado en {manifest_path}")

if __name__ == "__main__":
    import argparse
    parser = argparse.ArgumentParser(description="Lanzador de lotes carrusel con outfits base")
    parser.add_argument("--char", type=str, default="Komi", help="Personaje a generar: Komi, Rias, Asuna_Bunny, Nicole_Demara, all")
    parser.add_argument("--dry-run", action="store_true", help="Solo imprime los prompts sin llamar a Forge")
    args = parser.parse_args()
    
    chars_to_run = list(CUSTOM_CHARACTERS.keys()) if args.char == "all" else [args.char]
    for c in chars_to_run:
        generate_carousel_batch(c, dry_run=args.dry_run)
