"""Unit tests for ``memory.search.dto`` validation rules."""

from __future__ import annotations

import pytest
from pydantic import ValidationError

from everos.memory.search import (
    SearchData,
    SearchMethod,
    SearchRequest,
    SearchResponse,
)


def _minimal_request_kwargs() -> dict:
    return {
        "user_id": "alice",
        "query": "hello",
    }


def test_enable_llm_rerank_defaults_to_false() -> None:
    """HYBRID should NOT auto-trigger LLM Phase-5 rerank by default.

    The caller opts in explicitly when they want the extra LLM pass;
    leaving it off keeps a default HYBRID call cheap (no LLM ``chat``).
    """
    req = SearchRequest(**_minimal_request_kwargs())
    assert req.enable_llm_rerank is False


def test_enable_llm_rerank_accepts_true() -> None:
    req = SearchRequest(**_minimal_request_kwargs(), enable_llm_rerank=True)
    assert req.enable_llm_rerank is True


def test_minimal_request_uses_hybrid_default() -> None:
    req = SearchRequest(**_minimal_request_kwargs())
    assert req.method == SearchMethod.HYBRID
    assert req.top_k == -1
    assert req.include_profile is False
    assert req.filters is None
    assert req.radius is None


def test_top_k_zero_rejected() -> None:
    with pytest.raises(ValidationError) as exc:
        SearchRequest(**_minimal_request_kwargs(), top_k=0)
    assert "top_k" in str(exc.value)


def test_top_k_above_100_rejected() -> None:
    with pytest.raises(ValidationError):
        SearchRequest(**_minimal_request_kwargs(), top_k=101)


def test_top_k_below_minus_one_rejected() -> None:
    with pytest.raises(ValidationError):
        SearchRequest(**_minimal_request_kwargs(), top_k=-2)


def test_top_k_minus_one_accepted() -> None:
    req = SearchRequest(**_minimal_request_kwargs(), top_k=-1)
    assert req.top_k == -1


def test_top_k_in_range_accepted() -> None:
    req = SearchRequest(**_minimal_request_kwargs(), top_k=50)
    assert req.top_k == 50


def test_radius_out_of_range_rejected() -> None:
    with pytest.raises(ValidationError):
        SearchRequest(**_minimal_request_kwargs(), radius=1.5)
    with pytest.raises(ValidationError):
        SearchRequest(**_minimal_request_kwargs(), radius=-0.1)


def test_neither_user_id_nor_agent_id_rejected() -> None:
    """The xor validator requires exactly one of user_id / agent_id."""
    with pytest.raises(ValidationError, match="exactly one of"):
        SearchRequest(query="hello")  # neither set


def test_both_user_id_and_agent_id_rejected() -> None:
    """The xor validator rejects ambiguous owner identity."""
    with pytest.raises(ValidationError, match="exactly one of"):
        SearchRequest(user_id="alice", agent_id="agent_x", query="hello")


def test_empty_query_rejected() -> None:
    with pytest.raises(ValidationError):
        SearchRequest(user_id="alice", query="")


def test_empty_user_id_rejected() -> None:
    with pytest.raises(ValidationError):
        SearchRequest(user_id="", query="hello")


def test_extra_top_level_field_rejected() -> None:
    """``extra='forbid'`` keeps the contract tight."""
    with pytest.raises(ValidationError):
        SearchRequest(
            **_minimal_request_kwargs(),
            unexpected_field="x",  # type: ignore[call-arg]
        )


def test_filters_extra_keys_allowed() -> None:
    """FilterNode is open-shape; safety is enforced in the compiler."""
    req = SearchRequest(
        **_minimal_request_kwargs(),
        filters={"session_id": "sess_a", "AND": [{"timestamp": {"gte": 1}}]},
    )
    assert req.filters is not None
    dumped = req.filters.model_dump(exclude_none=True)
    assert dumped["session_id"] == "sess_a"
    assert dumped["AND"][0]["timestamp"]["gte"] == 1


def test_response_default_arrays_present() -> None:
    """Every ``data.*`` array must exist so callers can iterate unconditionally."""
    resp = SearchResponse(request_id="0" * 32, data=SearchData())
    assert resp.data.episodes == []
    assert resp.data.profiles == []
    assert resp.data.agent_cases == []
    assert resp.data.agent_skills == []


def test_method_enum_serialises_to_lowercase() -> None:
    req = SearchRequest(**_minimal_request_kwargs(), method="agentic")  # type: ignore[arg-type]
    assert req.method == SearchMethod.AGENTIC
    assert req.method.value == "agentic"
