"""Tests para GenerationConfig — serialización, roundtrip y frozen."""

import json
import sys

# Añadir python/functions/ml al path para que los imports internos del módulo
# (from lora_ref import LoraRef, from model_ref import ModelRef) funcionen.
# Los módulos se importan directamente desde el subdirectorio para evitar
# colisiones de tipos entre ml.generation_config.* y generation_config.*.
sys.path.insert(0, "python/functions/ml")

import pytest

from generation_config import GenerationConfig
from lora_ref import LoraRef
from model_ref import ModelRef


def _make_model() -> ModelRef:
    return ModelRef(name="stabilityai/stable-diffusion-v1-5", model_type="sd15")


def _make_config() -> GenerationConfig:
    return GenerationConfig(
        prompt="a cat in the moonlight",
        negative_prompt="blurry, low quality",
        seed=42,
        steps=30,
        cfg_scale=7.5,
        sampler="euler_a",
        width=512,
        height=512,
        model=_make_model(),
        loras=[],
        clip_skip=1,
    )


def test_instancia_ok():
    """GenerationConfig crea instancia sin errores"""
    cfg = _make_config()
    assert cfg.prompt == "a cat in the moonlight"
    assert cfg.seed == 42
    assert cfg.steps == 30
    assert cfg.cfg_scale == 7.5
    assert cfg.sampler == "euler_a"
    assert cfg.width == 512
    assert cfg.height == 512
    assert cfg.clip_skip == 1


def test_model_dump_keys_snake_case():
    """model_dump devuelve dict con keys snake_case incluyendo negative_prompt, cfg_scale, clip_skip"""
    try:
        from pydantic import BaseModel
    except ImportError:
        pytest.skip("pydantic no disponible")

    cfg = _make_config()
    d = cfg.model_dump()
    assert isinstance(d, dict)
    assert "negative_prompt" in d
    assert "cfg_scale" in d
    assert "clip_skip" in d
    assert d["negative_prompt"] == "blurry, low quality"
    assert d["cfg_scale"] == 7.5
    assert d["clip_skip"] == 1


def test_model_dump_json_parseable():
    """model_dump_json retorna str JSON parseable"""
    try:
        from pydantic import BaseModel
    except ImportError:
        pytest.skip("pydantic no disponible")

    cfg = _make_config()
    raw = cfg.model_dump_json()
    assert isinstance(raw, str)
    parsed = json.loads(raw)
    assert isinstance(parsed, dict)
    assert parsed["prompt"] == "a cat in the moonlight"
    assert parsed["seed"] == 42


def test_roundtrip_model_validate():
    """GenerationConfig.model_validate(json.loads(...)) roundtrip ok"""
    try:
        from pydantic import BaseModel
    except ImportError:
        pytest.skip("pydantic no disponible")

    cfg = _make_config()
    raw_json = cfg.model_dump_json()
    parsed = json.loads(raw_json)
    cfg2 = GenerationConfig.model_validate(parsed)
    assert cfg2.prompt == cfg.prompt
    assert cfg2.seed == cfg.seed
    assert cfg2.cfg_scale == cfg.cfg_scale
    assert cfg2.sampler == cfg.sampler
    assert cfg2.clip_skip == cfg.clip_skip
    assert cfg2.model.name == cfg.model.name
    assert cfg2.model.model_type == cfg.model.model_type


def test_frozen_levanta_error_al_mutar():
    """frozen: intentar mutar levanta AttributeError, ValidationError o FrozenInstanceError"""
    cfg = _make_config()
    raised = False
    try:
        # dataclass frozen y pydantic frozen levantan distintas excepciones
        cfg.prompt = "mutated"  # type: ignore[misc]
    except Exception:
        raised = True

    assert raised, "Se esperaba que mutar un campo frozen lanzara una excepcion"


def test_negative_prompt_opcional():
    """negative_prompt es opcional (default None)"""
    cfg = GenerationConfig(
        prompt="mountains",
        seed=0,
        steps=20,
        cfg_scale=7.0,
        sampler="euler",
        width=512,
        height=512,
        model=_make_model(),
    )
    assert cfg.negative_prompt is None
