"""Tests for query helpers and filter normalization."""

from __future__ import annotations

from typing import NoReturn

import pytest
from pytest import MonkeyPatch
from sqlmodel import Field, SQLModel, select
from tests.conftest import Book

from auen import FilterConfig, FilterFieldConfig
from auen._query import (
    _normalize_filter_values,
    apply_filters,
    apply_sorting,
    resolve_pk_field,
)


class NoPkModel(SQLModel):
    name: str


class CompositePkModel(SQLModel, table=True):
    id1: int = Field(primary_key=True)
    id2: int = Field(primary_key=True)


def test_resolve_pk_field_autodetect() -> None:
    name, _typ = resolve_pk_field(Book, None)
    assert name == "id"


def test_resolve_pk_field_fallback_on_inspect_error(monkeypatch: MonkeyPatch) -> None:
    def _boom(_model: type[SQLModel]) -> NoReturn:
        raise RuntimeError("inspect failed")

    monkeypatch.setattr("sqlalchemy.inspect", _boom)
    name, _typ = resolve_pk_field(Book, None)
    assert name == "id"


def test_resolve_pk_field_no_pk() -> None:
    with pytest.raises(ValueError, match="Cannot detect primary key"):
        resolve_pk_field(NoPkModel, None)


def test_resolve_pk_field_composite_pk() -> None:
    with pytest.raises(ValueError, match="Cannot detect primary key"):
        resolve_pk_field(CompositePkModel, None)


def test_resolve_pk_field_explicit() -> None:
    name, _typ = resolve_pk_field(Book, "title")
    assert name == "title"


def test_resolve_pk_field_invalid() -> None:
    with pytest.raises(ValueError, match="not found"):
        resolve_pk_field(Book, "nonexistent")


def test_resolve_pk_field_non_type_annotation_fallback(
    monkeypatch: MonkeyPatch,
) -> None:
    from auen import _query as query_mod

    class DummyModel(SQLModel):
        id: int

    DummyModel.model_fields["id"].annotation = "not-a-type"

    monkeypatch.setattr(query_mod, "get_origin", lambda _ann: None)
    name, pk_type = resolve_pk_field(DummyModel, "id")
    assert name == "id"
    assert pk_type is int


def test_resolve_pk_field_non_type_origin_args(monkeypatch: MonkeyPatch) -> None:
    from auen import _query as query_mod

    class DummyModel(SQLModel):
        id: int

    DummyModel.model_fields["id"].annotation = "not-a-type"

    monkeypatch.setattr(query_mod, "get_origin", lambda _ann: object())
    monkeypatch.setattr(query_mod, "get_args", lambda _ann: ("x",))
    name, pk_type = resolve_pk_field(DummyModel, "id")
    assert name == "id"
    assert pk_type is int


def test_normalize_list_values() -> None:
    result = _normalize_filter_values([1, 2, 3], int)
    assert result == [1, 2, 3]


def test_normalize_set_values() -> None:
    result = _normalize_filter_values({1}, int)
    assert result == [1]


def test_normalize_bool_values() -> None:
    result = _normalize_filter_values("true", bool)
    assert result == [True]

    result = _normalize_filter_values("false", bool)
    assert result == [False]


def test_normalize_non_castable() -> None:
    result = _normalize_filter_values("test", dict)
    assert result == ["test"]


def test_normalize_optional_int() -> None:
    result = _normalize_filter_values("1,2", int | None)
    assert result == [1, 2]


def test_normalize_cast_error() -> None:
    result = _normalize_filter_values("abc", int)
    assert result == ["abc"]


def test_normalize_filter_values_scalar_and_list_target() -> None:
    assert _normalize_filter_values(5, int) == [5]
    assert _normalize_filter_values("1,2", list[int]) == [1, 2]


def test_normalize_filter_values_bool_non_str() -> None:
    assert _normalize_filter_values(True, bool) == [True]


def test_normalize_filter_values_union_branch() -> None:
    assert _normalize_filter_values("1", int | None) == [1]


def test_normalize_filter_values_union_non_none() -> None:
    assert _normalize_filter_values("1", list[int] | None) == ["1"]


def test_normalize_filter_values_union_empty(monkeypatch: MonkeyPatch) -> None:
    from auen import _query as query_mod

    class Dummy:
        pass

    monkeypatch.setattr(query_mod, "get_origin", lambda _ann: Dummy)
    monkeypatch.setattr(query_mod, "get_args", lambda _ann: (type(None),))
    assert _normalize_filter_values("1", Dummy) == ["1"]


def test_apply_sorting_field_missing() -> None:
    query = select(Book)
    cfg = FilterConfig(sort_fields=["missing"])
    assert apply_sorting(query, Book, cfg, "missing") is query


def test_apply_filters_skip_branches() -> None:
    cfg = FilterConfig(
        fields={
            "title": FilterFieldConfig(ops=frozenset({"eq"})),
            "pages": FilterFieldConfig(ops=frozenset({"in"})),
            "missing": FilterFieldConfig(ops=frozenset({"eq"})),
        }
    )
    query = select(Book)
    result = apply_filters(
        query,
        Book,
        cfg,
        {
            "title": None,
            "title__lt": "1",
            "missing": "x",
            "pages__in": "",
        },
    )
    assert result is query


def test_apply_filters_in_non_empty() -> None:
    cfg = FilterConfig(fields={"pages": FilterFieldConfig(ops=frozenset({"in"}))})
    query = select(Book)
    result = apply_filters(
        query,
        Book,
        cfg,
        {"pages__in": "1,2"},
    )
    assert result is not query


def test_apply_filters_unknown_allowed_op() -> None:
    cfg = FilterConfig(fields={"title": FilterFieldConfig(ops=frozenset({"foo"}))})
    query = select(Book)
    result = apply_filters(
        query,
        Book,
        cfg,
        {"title__foo": "bar"},
    )
    assert result is query
