"""sdcpp_python_generate — genera una imagen con stable-diffusion-cpp-python a partir de GenerationConfig."""

from __future__ import annotations

import sys
import os
import time
from typing import Any

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

from generation_config import GenerationConfig
from image_gen_result import ImageGenResult

# Mapa de sampler del registry al nombre que espera stable-diffusion-cpp-python
_SAMPLER_MAP: dict[str, str] = {
    "euler": "euler",
    "euler_a": "euler_a",
    "dpm++2m": "dpmpp2m",
    "dpm++2m_v2": "dpmpp2mv2",
    "heun": "heun",
    "dpm2": "dpm2",
    "lcm": "lcm",
}


def sdcpp_python_generate(sd: Any, cfg: GenerationConfig) -> ImageGenResult:
    """Genera una imagen con un objeto StableDiffusion usando GenerationConfig como contrato.

    Mapea los campos del GenerationConfig canonico a los parametros de
    StableDiffusion.generate_image(). Mide la duracion total de la llamada.
    Retorna un ImageGenResult con la primera imagen del batch, metadata del backend
    y duracion en milisegundos. VRAM no se mide (None).

    Args:
        sd:  Instancia StableDiffusion cargada via sdcpp_python_load.
        cfg: Contrato de parametros de generacion. Todos los campos son leidos.
             cfg.sampler debe ser uno de los valores del SamplerName del registry.

    Returns:
        ImageGenResult con image=PIL.Image, meta con backend/modelo/sampler/seed/wtype,
        duration_ms medido via time.perf_counter(), vram_peak_mb=None.

    Raises:
        KeyError:   Si cfg.sampler no tiene correspondencia en _SAMPLER_MAP.
        ImportError: Si stable_diffusion_cpp no esta instalado.
        RuntimeError: Si generate_image retorna lista vacia o None.
    """
    try:
        from stable_diffusion_cpp import StableDiffusion  # noqa: F401 — verifica disponibilidad
    except ImportError as exc:
        raise ImportError(
            "sdcpp_python_generate requiere stable-diffusion-cpp-python. "
            "Instalar con: pip install stable-diffusion-cpp-python"
        ) from exc

    sample_method = _SAMPLER_MAP.get(cfg.sampler)
    if sample_method is None:
        raise KeyError(
            f"Sampler '{cfg.sampler}' no tiene correspondencia en sdcpp_python_generate. "
            f"Valores soportados: {list(_SAMPLER_MAP.keys())}"
        )

    # wtype del objeto sd (para metadata)
    wtype = getattr(sd, "wtype", "unknown")

    t0 = time.perf_counter()

    images = sd.generate_image(
        prompt=cfg.prompt,
        negative_prompt=cfg.negative_prompt or "",
        cfg_scale=cfg.cfg_scale,
        sample_method=sample_method,
        sample_steps=cfg.steps,
        seed=cfg.seed,
        width=cfg.width,
        height=cfg.height,
        clip_skip=cfg.clip_skip if cfg.clip_skip is not None else -1,
        batch_count=1,
    )

    duration_ms = int((time.perf_counter() - t0) * 1000)

    if not images:
        raise RuntimeError(
            "sdcpp_python_generate: generate_image retorno lista vacia o None."
        )

    meta: dict[str, Any] = {
        "backend": "sdcpp_python",
        "model": cfg.model.name,
        "sampler": cfg.sampler,
        "actual_steps": cfg.steps,
        "seed": cfg.seed,
        "wtype": wtype,
    }

    return ImageGenResult(
        image=images[0],
        meta=meta,
        duration_ms=duration_ms,
        vram_peak_mb=None,
    )
