import os
import sys
import random
import logging
import yaml
from datetime import datetime

sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))

from api_client import ForgeClient, VisionClient
from prompt_builder import PromptBuilder
from vision_critic import VisionCritic

try:
    from prompt_designer import PromptDesigner, LiterouterDesigner
except ImportError:
    PromptDesigner = None
    LiterouterDesigner = None

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
CONFIG_DIR = os.path.join(BASE_DIR, "Config")
OUTPUT_DIR = os.path.join(BASE_DIR, "Output")
LOGS_DIR = os.path.join(BASE_DIR, "Logs")

os.makedirs(OUTPUT_DIR, exist_ok=True)
os.makedirs(LOGS_DIR, exist_ok=True)

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s [%(levelname)s] %(message)s",
    handlers=[
        logging.FileHandler(os.path.join(LOGS_DIR, "engine.log"), encoding="utf-8"),
        logging.StreamHandler()
    ]
)
logger = logging.getLogger(__name__)


def load_config():
    config_path = os.path.join(CONFIG_DIR, "plumloom_engine.yaml")
    with open(config_path, "r", encoding="utf-8") as f:
        return yaml.safe_load(f)


def init_clients(config):
    forge_cfg = config["endpoints"]["forge_neo"]
    forge = ForgeClient(url=forge_cfg["url"], timeout=forge_cfg["timeout"])

    vision_cfg = config["endpoints"]["vision"]
    vision = VisionClient(
        url=vision_cfg["url"],
        model=vision_cfg["model"],
        timeout=vision_cfg["timeout"]
    )

    designer_cfg = config.get("prompt_designer", {})
    primary = designer_cfg.get("primary", "literouter")

    if PromptDesigner is None and LiterouterDesigner is None:
        logger.warning("prompt_designer no disponible: menu IA (7/8/9) desactivado")
        return forge, vision, None

    if primary == "literouter":
        lit_cfg = config["endpoints"]["literouter"]
        api_keys_file = os.path.join(BASE_DIR, "CONTEXTO", "apis literouter.txt")
        api_keys = []
        if os.path.exists(api_keys_file):
            with open(api_keys_file, "r", encoding="utf-8") as f:
                for line in f:
                    line = line.strip()
                    if line.startswith("key") and "=" in line:
                        key = line.split("=", 1)[1].strip()
                        if key:
                            api_keys.append(key)
        
        if not api_keys:
            api_keys = [os.environ.get("LITEROUTER_API_KEY", "")]
        
        designer = LiterouterDesigner(
            api_keys=api_keys,
            models=lit_cfg.get("models", ["deepseek-v3.2:free"]),
            timeout=lit_cfg.get("timeout", 30),
            temperature=designer_cfg.get("temperature", 0.7)
        )
    else:
        omni_cfg = config["endpoints"]["omniroute"]
        api_key = os.environ.get("OMNIROUTE_API_KEY", "sk-9e3027503ec29237-25cded-bd502dd0")
        designer = PromptDesigner(
            omniroute_url=omni_cfg["url"],
            omniroute_key=api_key,
            model=designer_cfg.get("model", "cohere/command-r-plus-08-2024"),
            temperature=designer_cfg.get("temperature", 0.7)
        )

    return forge, vision, designer


def print_header():
    print("\n" + "=" * 55)
    print("       PLUMLOOM ENGINE v2 - PANEL DE GENERACION")
    print("=" * 55)


def print_menu(designer=None):
    print("\n[1] Generar lote sugerente (X/Twitter)")
    print("[2] Generar lote explícito (Patreon)")
    print("[3] Generar imagen individual")
    print("[4] Ver personajes disponibles")
    print("[5] Ver wildcards disponibles")
    print("[6] Verificar conexión Forge Neo")
    print("[7] Verificar conexión Diseñador IA")
    print("[8] Generar prompts con IA")
    print("[9] Generar lote con IA (prompts + imagen)")
    if designer is None:
        print("    [7][8][9] desactivados: modulo de diseno IA no instalado")
    print("[0] Salir")
    print("-" * 55)


def show_characters(builder):
    names = builder.get_character_names()
    print(f"\n--- Personajes disponibles ({len(names)}) ---")
    for i, name in enumerate(names, 1):
        char = builder.get_character(name)
        has_outfit = "✓" if char.get("outfit") else "×"
        print(f"  {i:3d}. {name} [outfit: {has_outfit}]")


def show_wildcards(builder):
    for wc_type in ["pose", "scene", "details", "outfit"]:
        options = builder.get_wildcard_options(wc_type)
        print(f"\n--- {wc_type.upper()} ({len(options)} opciones) ---")
        for i, opt in enumerate(options, 1):
            print(f"  {i:2d}. {opt[:70]}")


def check_forge(forge):
    print("\nVerificando conexión con Forge Neo...")
    if forge.check_status():
        print("✓ Forge Neo CONECTADO y respondiendo")
        try:
            opts = forge.get_options()
            model = opts.get("sd_model_checkpoint", "desconocido")
            print(f"  Modelo activo: {model}")
        except Exception:
            pass
    else:
        print("✗ Forge Neo NO responde. Verifica que esté abierto con --api")


def check_command_r(designer):
    if designer is None:
        print("\n✗ Diseno IA no disponible (DeepSeek retirado del stack).")
        return

    designer_type = "Literouter (DeepSeek)" if isinstance(designer, LiterouterDesigner) else "Omniroute (Command R+)"
    print(f"\nVerificando conexión con {designer_type}...")
    ok, msg = designer.check_connection()
    if ok:
        print(f"✓ {designer_type} CONECTADO y respondiendo")
        print(f"  Respuesta: {msg}")
    else:
        print(f"✗ {designer_type} NO responde: {msg}")


def run_ai_prompt_generation(designer, builder):
    if designer is None:
        print("\n✗ Diseno IA no disponible (DeepSeek retirado del stack).")
        return

    designer_type = "Literouter" if isinstance(designer, LiterouterDesigner) else "Command R+"
    print(f"\n--- Generación de Prompts con IA ({designer_type}) ---")

    names = builder.get_character_names()
    print(f"\nPersonajes disponibles: {len(names)}")
    personaje = input("\nNombre del personaje: ").strip()
    if not personaje:
        print("Debes especificar un personaje")
        return

    char = builder.get_character(personaje)
    if not char:
        print(f"Personaje '{personaje}' no encontrado")
        return

    print(f"\nTags base: {char['base'][:80]}...")
    spice = input("Nivel de contenido (sugerente/explicito) [sugerente]: ").strip().lower()
    if spice not in ["sugerente", "explicito"]:
        spice = "sugerente"

    spice_level = "suggestive" if spice == "sugerente" else "explicit"

    outfit_hint = input("Idea de outfit (vacío = IA decide): ").strip() or None
    scene_hint = input("Idea de escenario (vacío = IA decide): ").strip() or None

    print("\nGenerando prompts con Command R+...")
    try:
        prompt_text, tokens = designer.design_prompt(
            character_name=personaje,
            character_tags=char["base"],
            spice_level=spice_level,
            outfit_hint=outfit_hint,
            scene_hint=scene_hint
        )
        print(f"\n{'=' * 55}")
        print("PROMPT GENERADO POR IA:")
        print(f"{'=' * 55}")
        print(prompt_text)
        print(f"{'=' * 55}")
        print(f"Tokens usados: {tokens}")
        print(f"Longitud: {len(prompt_text)} caracteres")

        usar = input("\n¿Usar este prompt para generar imagen? (s/n) [s]: ").strip().lower()
        if usar != "n":
            return prompt_text
    except Exception as e:
        print(f"Error generando prompt: {e}")
        return None

    return None


def run_ai_batch(config, forge, vision, builder, designer, spice_level="suggestive"):
    if designer is None:
        print("\n✗ Diseno IA no disponible (DeepSeek retirado del stack).")
        return

    gen_cfg = config["generation"]
    designer_type = "Literouter" if isinstance(designer, LiterouterDesigner) else "Command R+"

    print(f"\n--- Lote con IA ({designer_type}): {spice_level} ---")

    names = builder.get_character_names()
    print(f"\nPersonajes disponibles: {len(names)}")
    personaje = input("\nNombre del personaje (vacío = aleatorio): ").strip()

    if personaje:
        char = builder.get_character(personaje)
        if not char:
            print(f"Personaje '{personaje}' no encontrado")
            return
    else:
        personaje = random.choice(names)
        char = builder.get_character(personaje)
        print(f"Personaje aleatorio: {personaje}")

    cantidad_str = input("Cantidad de imágenes [5]: ").strip()
    try:
        cantidad = int(cantidad_str)
    except ValueError:
        cantidad = 5

    outfit_hint = input("Idea de outfit (vacío = IA decide): ").strip() or None
    scene_hint = input("Idea de escenario (vacío = IA decide): ").strip() or None

    print(f"\nGenerando {cantidad} prompts con {designer_type}...")
    try:
        prompts, tokens = designer.design_batch(
            character_name=personaje,
            character_tags=char["base"],
            spice_level=spice_level,
            count=cantidad,
            outfit_hint=outfit_hint,
            scene_hint=scene_hint
        )
        print(f"Prompts generados: {len(prompts)} ({tokens} tokens)")
    except Exception as e:
        print(f"Error generando prompts: {e}")
        return

    print(f"\n{'=' * 55}")
    print("PROMPTS GENERADOS:")
    print(f"{'=' * 55}")
    for i, p in enumerate(prompts, 1):
        print(f"\n[{i}] {p[:100]}...")
    print(f"\n{'=' * 55}")

    confirmar = input("\n¿Generar imágenes con estos prompts? (s/n) [s]: ").strip().lower()
    if confirmar == "n":
        print("Cancelado")
        return

    critic = VisionCritic(
        vision_client=vision,
        max_retries=config["critic"]["max_retries"],
        auto_approve_on_error=config["critic"]["auto_approve_on_error"]
    )

    if spice_level == "suggestive":
        output_subdir = "campaigns_X"
        extra_tags = "suggestive, cleavage"
    else:
        output_subdir = "patreon_NSFW"
        extra_tags = "undressing, bare shoulders, highly suggestive pose"

    output_path = os.path.join(OUTPUT_DIR, output_subdir)
    os.makedirs(output_path, exist_ok=True)

    aprobadas = 0
    intentos = 0
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")

    for i, prompt_ai in enumerate(prompts):
        if aprobadas >= cantidad:
            break

        intentos += 1
        print(f"\n[Imagen {i+1}/{len(prompts)}] Progreso: {aprobadas}/{cantidad}")

        try:
            positive = f"{builder.master_prompt}, {char['base']}, age up, mature female, {prompt_ai}, {extra_tags}"
            negative = builder.negative_prompt

            print(f"  Prompt: {positive[:80]}...")

            images, info = forge.txt2img(
                prompt=positive,
                negative_prompt=negative,
                width=gen_cfg["width"],
                height=gen_cfg["height"],
                steps=gen_cfg["steps"],
                cfg_scale=gen_cfg["cfg_scale"],
                sampler_name=gen_cfg["sampler_name"],
                clip_skip=gen_cfg["clip_skip"]
            )

            if not images:
                print("  Forge no devolvió imagen")
                continue

            image_bytes = images[0]
            seed = info.get("seed", random.randint(1, 999999999))

            print("  Enviando a crítico visual...")
            approved, verdict, details = critic.full_audit(image_bytes)

            if approved:
                aprobadas += 1
                filename = f"{timestamp}_ai_{aprobadas:03d}_seed{seed}.png"
                filepath = os.path.join(output_path, filename)
                forge.save_image(image_bytes, filepath)
                print(f"  ✓ APROBADA [{aprobadas}/{cantidad}] → {filename}")
            else:
                rejected_path = os.path.join(OUTPUT_DIR, "rejected", f"{timestamp}_ai_rej_{intentos}.png")
                forge.save_image(image_bytes, rejected_path)
                print(f"  ✗ RECHAZADA: {verdict}")

        except Exception as e:
            logger.error(f"Error en imagen {i+1}: {e}")
            print(f"  ✗ Error: {e}")

    print(f"\n{'=' * 55}")
    print(f"  LOTE IA COMPLETADO")
    print(f"  Aprobadas: {aprobadas}/{cantidad}")
    print(f"  Carpeta: {output_path}")
    print(f"{'=' * 55}\n")


def get_batch_params(spice_level):
    print(f"\n--- Configuración del lote ({spice_level}) ---")

    personaje = input("Personaje (vacío = aleatorio): ").strip()
    if not personaje:
        personaje = None

    outfit = input("Outfit (vacío = aleatorio): ").strip()
    if not outfit:
        outfit = None

    scene = input("Escenario (vacío = aleatorio): ").strip()
    if not scene:
        scene = None

    cantidad_str = input("Cantidad de imágenes: ").strip()
    try:
        cantidad = int(cantidad_str)
    except ValueError:
        cantidad = 5

    return personaje, outfit, scene, cantidad


def run_batch(config, forge, vision, builder, spice_level="suggestive"):
    gen_cfg = config["generation"]

    personaje, outfit, scene, cantidad = get_batch_params(spice_level)

    critic = VisionCritic(
        vision_client=vision,
        max_retries=config["critic"]["max_retries"],
        auto_approve_on_error=config["critic"]["auto_approve_on_error"]
    )

    if spice_level == "suggestive":
        output_subdir = "campaigns_X"
        extra_tags = "suggestive, cleavage"
    else:
        output_subdir = "patreon_NSFW"
        extra_tags = "undressing, bare shoulders, highly suggestive pose"

    output_path = os.path.join(OUTPUT_DIR, output_subdir)
    os.makedirs(output_path, exist_ok=True)

    aprobadas = 0
    intentos = 0
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")

    print(f"\n{'=' * 55}")
    print(f"  Iniciando lote: {cantidad} imágenes ({spice_level})")
    print(f"  Personaje: {personaje or 'ALEATORIO'}")
    print(f"  Outfit: {outfit or 'ALEATORIO'}")
    print(f"  Escenario: {scene or 'ALEATORIO'}")
    print(f"  Carpeta: {output_subdir}/")
    print(f"{'=' * 55}\n")

    while aprobadas < cantidad:
        intentos += 1
        print(f"[Intento #{intentos}] Progreso: {aprobadas}/{cantidad}")

        try:
            if personaje:
                prompt_pos, prompt_neg = builder.build_prompt(
                    character_name=personaje,
                    outfit=outfit,
                    scene=scene,
                    extra_tags=extra_tags
                )
            else:
                random_char = random.choice(builder.get_character_names())
                prompt_pos, prompt_neg = builder.build_random(
                    character_name=random_char,
                    spice_level=spice_level
                )
                print(f"  Personaje aleatorio: {random_char}")

            print(f"  Prompt: {prompt_pos[:80]}...")

            images, info = forge.txt2img(
                prompt=prompt_pos,
                negative_prompt=prompt_neg,
                width=gen_cfg["width"],
                height=gen_cfg["height"],
                steps=gen_cfg["steps"],
                cfg_scale=gen_cfg["cfg_scale"],
                sampler_name=gen_cfg["sampler_name"],
                clip_skip=gen_cfg["clip_skip"]
            )

            if not images:
                print("  ✗ Forge no devolvió imágenes")
                continue

            image_bytes = images[0]
            seed = info.get("seed", random.randint(1, 999999999))

            print("  Enviando a crítico visual...")
            approved, verdict, details = critic.full_audit(image_bytes)

            if approved:
                aprobadas += 1
                filename = f"{timestamp}_{aprobadas:03d}_seed{seed}.png"
                filepath = os.path.join(output_path, filename)
                forge.save_image(image_bytes, filepath)
                print(f"  ✓ APROBADA [{aprobadas}/{cantidad}] → {filename}")
            else:
                rejected_path = os.path.join(OUTPUT_DIR, "rejected", f"{timestamp}_rej_{intentos}.png")
                forge.save_image(image_bytes, rejected_path)
                print(f"  ✗ RECHAZADA: {verdict}")

        except Exception as e:
            logger.error(f"Error en intento {intentos}: {e}")
            print(f"  ✗ Error: {e}")
            continue

    print(f"\n{'=' * 55}")
    print(f"  LOTE COMPLETADO")
    print(f"  Aprobadas: {aprobadas}/{cantidad}")
    print(f"  Intentos totales: {intentos}")
    print(f"  Tasa de éxito: {aprobadas/intentos*100:.1f}%")
    print(f"  Carpeta: {output_path}")
    print(f"{'=' * 55}\n")


def run_single(config, forge, vision, builder):
    print("\n--- Generación Individual ---")

    personaje = input("Personaje: ").strip()
    if not personaje:
        print("Debes especificar un personaje")
        return

    outfit = input("Outfit (vacío = del personaje): ").strip() or None
    scene = input("Escenario (vacío = aleatorio): ").strip() or None
    pose = input("Pose (vacío = aleatoria): ").strip() or None
    details = input("Detalles (vacío = aleatorio): ").strip() or None

    gen_cfg = config["generation"]

    prompt_pos, prompt_neg = builder.build_prompt(
        character_name=personaje,
        outfit=outfit,
        scene=scene,
        pose=pose,
        details=details
    )

    print(f"\nPrompt: {prompt_pos[:100]}...")
    print("Generando...")

    images, info = forge.txt2img(
        prompt=prompt_pos,
        negative_prompt=prompt_neg,
        width=gen_cfg["width"],
        height=gen_cfg["height"],
        steps=gen_cfg["steps"],
        cfg_scale=gen_cfg["cfg_scale"],
        sampler_name=gen_cfg["sampler_name"],
        clip_skip=gen_cfg["clip_skip"]
    )

    if images:
        seed = info.get("seed", 0)
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        filename = f"single_{timestamp}_seed{seed}.png"
        filepath = os.path.join(OUTPUT_DIR, "campaigns_X", filename)
        forge.save_image(images[0], filepath)
        print(f"✓ Guardada: {filepath}")
    else:
        print("✗ No se generó ninguna imagen")


def main():
    try:
        config = load_config()
    except Exception as e:
        print(f"Error cargando configuración: {e}")
        return

    forge, vision, designer = init_clients(config)
    builder = PromptBuilder(BASE_DIR)

    if designer is None:
        designer_type = "ninguno (DeepSeek retirado)"
        designer_model = "-"
    else:
        designer_type = "Literouter (DeepSeek)" if isinstance(designer, LiterouterDesigner) else "Omniroute (Command R+)"
        designer_model = designer._get_current_model() if isinstance(designer, LiterouterDesigner) else designer.model

    print_header()
    print(f"Personajes: {len(builder.get_character_names())}")
    print(f"Wildcards: {len(builder.wildcards)} tipos cargados")
    print(f"Diseñador: {designer_type} ({designer_model})")

    while True:
        print_menu(designer)
        opcion = input("Selecciona una opción: ").strip()

        if opcion == "1":
            run_batch(config, forge, vision, builder, "suggestive")
        elif opcion == "2":
            run_batch(config, forge, vision, builder, "explicit")
        elif opcion == "3":
            run_single(config, forge, vision, builder)
        elif opcion == "4":
            show_characters(builder)
        elif opcion == "5":
            show_wildcards(builder)
        elif opcion == "6":
            check_forge(forge)
        elif opcion == "7":
            check_command_r(designer)
        elif opcion == "8":
            run_ai_prompt_generation(designer, builder)
        elif opcion == "9":
            run_ai_batch(config, forge, vision, builder, designer, "suggestive")
        elif opcion == "0":
            print("\nHasta luego!")
            break
        else:
            print("Opcion no valida")


if __name__ == "__main__":
    main()