"""Unit tests for property data models."""

from enum import IntEnum

import pytest

from pydantic import TypeAdapter, ValidationError

from topix.agents.datatypes.outputs import WebSearchOutput
from topix.agents.datatypes.reasoning_step import ReasoningStep
from topix.agents.datatypes.tool_call import ToolCall
from topix.agents.datatypes.tools import AgentToolName
from topix.datatypes.property import (
    DataProperty,
    IconProperty,
    KeywordProperty,
    MultiKeywordProperty,
    MultiTextProperty,
    NumberProperty,
    PropertyType,
    ReasoningProperty,
    TextProperty,
)


def test_number_property_sets_id_and_type():
    """NumberProperty should auto-generate an id and enforce its type."""
    prop = NumberProperty(number=3.14)

    assert prop.type is PropertyType.NUMBER
    assert prop.number == 3.14
    assert isinstance(prop.id, str)
    # gen_uid returns a hyphen-less UUID (32 chars)
    assert len(prop.id) == 32


def test_icon_property_accepts_icon_and_emoji_variants():
    """IconProperty should parse both icon URLs and emoji payloads."""
    icon_prop = IconProperty(icon={"type": "icon", "icon": "http://example.com/icon.png"})
    emoji_prop = IconProperty(icon={"type": "emoji", "emoji": ":)"})

    assert icon_prop.icon.type == "icon"
    assert icon_prop.icon.icon.endswith("icon.png")
    assert emoji_prop.icon.type == "emoji"
    assert emoji_prop.icon.emoji == ":)"


def test_icon_property_accepts_phosphor_variant():
    """IconProperty should parse the phosphor variant with name + optional color."""
    bare = IconProperty(icon={"type": "phosphor", "name": "Lightbulb"})
    tinted = IconProperty(
        icon={"type": "phosphor", "name": "Rocket", "color": "#dc2626"}
    )
    themed = IconProperty(
        icon={"type": "phosphor", "name": "Heart", "color": "var(--color-foreground)"}
    )

    assert bare.icon.type == "phosphor"
    assert bare.icon.name == "Lightbulb"
    assert bare.icon.color is None

    assert tinted.icon.name == "Rocket"
    assert tinted.icon.color == "#dc2626"

    assert themed.icon.color == "var(--color-foreground)"


def test_icon_property_round_trips_phosphor_variant_through_dump():
    """The phosphor variant should survive a model_dump / re-parse cycle.

    Mirrors how the wire serializes properties for persistence; if this
    drops fields the apply_ops lift would silently lose icon state.
    """
    original = IconProperty(
        icon=IconProperty.Phosphor(name="MapPin", color="#2563eb")
    )

    raw = original.model_dump()
    restored = IconProperty.model_validate(raw)

    assert restored.icon.type == "phosphor"
    assert restored.icon.name == "MapPin"
    assert restored.icon.color == "#2563eb"


def test_icon_property_rejects_phosphor_without_name():
    """A missing `name` should fail validation rather than coerce."""
    with pytest.raises(ValidationError):
        IconProperty(icon={"type": "phosphor"})


def test_mutable_defaults_are_not_shared_between_instances():
    """Each instance should get its own list defaults."""
    first_texts = MultiTextProperty()
    first_texts.texts.append("first")

    second_texts = MultiTextProperty()
    assert second_texts.texts == []

    first_keywords = MultiKeywordProperty()
    first_keywords.values.append("alpha")

    second_keywords = MultiKeywordProperty()
    assert second_keywords.values == []


def test_keyword_property_retains_enum_type_metadata():
    """value_type should capture the enum class used for keyword values."""

    class Priority(IntEnum):
        LOW = 1
        HIGH = 2

    prop = KeywordProperty(value=Priority.HIGH, value_type=Priority)

    assert prop.value == Priority.HIGH
    assert prop.value_type is Priority


def test_data_property_discriminates_by_type():
    """Discriminated union should produce the right model from raw data."""
    adapter = TypeAdapter(DataProperty)
    parsed = adapter.validate_python({"type": "text", "text": "hello", "searchable": True})

    assert isinstance(parsed, TextProperty)
    assert parsed.text == "hello"
    assert parsed.searchable is True


def test_data_property_rejects_unknown_type():
    """Invalid discriminator values should raise a validation error."""
    adapter = TypeAdapter(DataProperty)

    with pytest.raises(ValidationError):
        adapter.validate_python({"type": "not_a_property", "value": "nope"})


def test_reasoning_property_accepts_reasoning_steps_and_tool_calls():
    """ReasoningProperty should support the new mixed reasoning step union."""
    prop = ReasoningProperty(
        reasoning=[
            ReasoningStep(reasoning="Need more context", message="Checking one detail"),
            ToolCall(
                id="tool-1",
                name=AgentToolName.WEB_SEARCH,
                output=WebSearchOutput(search_results=[]),
                arguments={"query": "latest gdp france"},
            ),
        ]
    )

    assert isinstance(prop.reasoning[0], ReasoningStep)
    assert isinstance(prop.reasoning[1], ToolCall)
