from typing import Any, cast

import pytest

from crystflux.v1 import Crystallizer
from crystflux.v1.adapters import pydantic as adapter

from pydantic import BaseModel
from pydantic import ValidationError


class Author(BaseModel):
    author: str
    author_en: str
    quotes: list[str]


class PayloadHolder(BaseModel):
    payload: Any





def test_to_pydantic_strict_success(tao_data: dict[str, Any]) -> None:
    strict = Crystallizer.strict(tao_data["laozi"])
    model = adapter.to_pydantic(Author, strict)
    assert model.author == "老子"
    assert model.author_en == "Laozi"
    assert model.quotes[0].startswith("道可道")


def test_from_pydantic_roundtrip() -> None:
    model = Author(author="老子", author_en="Laozi", quotes=["道可道，非常道"])
    value = adapter.from_pydantic(model)
    expected = Crystallizer.strict(model.model_dump(mode="python"))
    assert value.as_json_value() == expected.as_json_value()


def test_strict_roundtrip_value_pydantic(tao_data: dict[str, Any]) -> None:
    laozi = cast(dict[str, Any], tao_data["laozi"])
    subset = {
        "author": laozi["author"],
        "author_en": laozi["author_en"],
        "quotes": laozi["quotes"],
    }
    strict = Crystallizer.strict(subset)
    model = adapter.to_pydantic(Author, strict)
    back = adapter.from_pydantic(model)
    assert back.as_json_value() == strict.as_json_value()


def test_to_pydantic_rejects_latent(tao_data: dict[str, Any]) -> None:
    latent = Crystallizer.latent(tao_data["laozi"])
    with pytest.raises(TypeError):
        adapter.to_pydantic(Author, latent)  # type: ignore[arg-type]

def test_to_pydantic_rejects_wrong_model_type(tao_data: dict[str, Any]) -> None:
    class NotAModel:
        pass

    strict = Crystallizer.strict(tao_data["laozi"])
    with pytest.raises(TypeError, match="model_cls must be a subclass of BaseModel"):
        adapter.to_pydantic(NotAModel, strict)  # type: ignore


def test_to_pydantic_validation_error() -> None:
    bad = Crystallizer.strict({"author": 123, "author_en": "X", "quotes": [1, 2]})
    with pytest.raises(ValidationError):
        adapter.to_pydantic(Author, bad)


def test_pydantic_field_accepts_jsonvalue() -> None:
    data = {"payload": {"x": ["y", 1], "flag": True}}
    strict = Crystallizer.strict(data)
    model = adapter.to_pydantic(PayloadHolder, strict)
    # Use as_json_value() to get comparable JSON value
    assert model.payload == strict.get("payload").as_json_value()


def test_from_pydantic_rejects_non_basemodel() -> None:
    class NotAModel:
        def model_dump(self, mode: str) -> Any:  # pragma: no cover - signature mimic
            return {"x": 1}

    with pytest.raises(TypeError, match="BaseModel instance"):
        adapter.from_pydantic(NotAModel())  # type: ignore[arg-type]
