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

from __future__ import annotations

import enum
from types import SimpleNamespace
from typing import NoReturn, cast

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

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


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({FilterOp.EQ})),
            "pages": FilterFieldConfig(ops=frozenset({FilterOp.IN})),
            "missing": FilterFieldConfig(ops=frozenset({FilterOp.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({FilterOp.IN}))})
    query = select(Book)
    result = apply_filters(
        query,
        Book,
        cfg,
        {"pages__in": "1,2"},
    )
    assert result is not query


def test_apply_filters_in_empty_values_noop() -> None:
    cfg = FilterConfig(fields={"id": FilterFieldConfig(ops=frozenset({FilterOp.IN}))})
    query = select(Book)
    result = apply_filters(
        query,
        Book,
        cfg,
        {"id__in": ","},
    )
    assert result is query


def test_apply_filters_in_empty_values_then_next_filter() -> None:
    cfg = FilterConfig(
        fields={
            "id": FilterFieldConfig(ops=frozenset({FilterOp.IN})),
            "title": FilterFieldConfig(ops=frozenset({FilterOp.EQ})),
        }
    )
    query = select(Book)
    result = apply_filters(
        query,
        Book,
        cfg,
        {"id__in": ",", "title": "X"},
    )
    assert result is not query


def test_apply_filters_unknown_op_raises() -> None:
    cfg = FilterConfig(fields={"title": FilterFieldConfig(ops=frozenset({FilterOp.EQ}))})
    query = select(Book)
    with pytest.raises(ConfigurationError, match="Unknown filter operation"):
        apply_filters(
            query,
            Book,
            cfg,
            {"title__foo": "bar"},
        )


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

    class FakeFilterOp(enum.Enum):
        EQ = "eq"
        LT = "lt"
        LTE = "lte"
        GT = "gt"
        GTE = "gte"
        CONTAINS = "contains"
        ICONTAINS = "icontains"
        IN = "in"
        NOOP = "noop"

    monkeypatch.setattr(query_mod, "FilterOp", FakeFilterOp)
    cfg = cast(
        FilterConfig,
        SimpleNamespace(
            fields={"id": SimpleNamespace(ops=frozenset({FakeFilterOp.NOOP}))}
        ),
    )
    query = select(Book)
    result = query_mod.apply_filters(query, Book, cfg, {"id__noop": "1"})
    assert result is query
