"""Tests for the luci Python bindings."""

from __future__ import annotations

import shutil
import tempfile
from pathlib import Path
from typing import Any, cast

import luci


def make_index(
    mapping: dict[str, object] | None = None,
) -> tuple[str, luci.Index]:
    """Create a temporary index for testing."""
    path = tempfile.mkdtemp(prefix="luci_pytest_")
    if mapping is None:
        mapping = {
            "properties": {
                "title": {"type": "text"},
                "body": {"type": "text"},
                "tag": {"type": "keyword"},
                "price": {"type": "float"},
            },
        }
    idx = luci.Index.create(str(Path(path) / "idx"), mapping)
    return path, idx


def test_create_and_search() -> None:
    """Search returns matching documents with correct source."""
    path, index = make_index()
    try:
        index.add({"title": "hello world", "tag": "greeting"})
        index.commit()

        results = index.search({"match": {"title": "hello"}})
        assert results["total_hits"]["value"] == 1
        assert results["hits"][0]["_source"]["title"] == "hello world"
    finally:
        shutil.rmtree(path)


def test_term_query() -> None:
    """Term query matches exact keyword values."""
    path, index = make_index()
    try:
        index.add({"title": "A", "tag": "tech"})
        index.add({"title": "B", "tag": "pets"})
        index.add({"title": "C", "tag": "tech"})
        index.commit()

        results = index.search({"term": {"tag": "tech"}})
        assert results["total_hits"]["value"] == 2
    finally:
        shutil.rmtree(path)


def test_bool_query() -> None:
    """Bool query combines must and filter clauses."""
    path, index = make_index()
    try:
        index.add({"title": "search engine", "tag": "tech"})
        index.add({"title": "search tips", "tag": "blog"})
        index.add({"title": "database design", "tag": "tech"})
        index.commit()

        results = index.search(
            {
                "bool": {
                    "must": [{"match": {"title": "search"}}],
                    "filter": [{"term": {"tag": "tech"}}],
                },
            },
        )
        assert results["total_hits"]["value"] == 1
    finally:
        shutil.rmtree(path)


def test_aggregations() -> None:
    """Aggregations compute correct bucket counts and metric values."""
    path, index = make_index()
    try:
        index.add({"title": "A", "tag": "tech", "price": 10.0})
        index.add({"title": "B", "tag": "tech", "price": 20.0})
        index.add({"title": "C", "tag": "pets", "price": 5.0})
        index.commit()

        results = index.search(
            {
                "query": {"match_all": {}},
                "aggs": {
                    "by_tag": {"terms": {"field": "tag"}},
                    "avg_price": {"avg": {"field": "price"}},
                },
                "size": 0,
            },
        )

        assert "aggregations" in results
        aggs = cast("dict[str, Any]", results["aggregations"])

        avg_val = cast("float", aggs["avg_price"]["value"])
        assert abs(avg_val - 11.67) < 1.0

        buckets = cast("list[dict[str, Any]]", aggs["by_tag"]["buckets"])
        tag_counts = {b["key"]: b["doc_count"] for b in buckets}
        assert tag_counts["tech"] == 2
        assert tag_counts["pets"] == 1
    finally:
        shutil.rmtree(path)


def test_phrase_query() -> None:
    """Phrase query matches exact word order."""
    path, index = make_index()
    try:
        index.add({"body": "the quick brown fox"})
        index.add({"body": "brown quick fox"})
        index.commit()

        results = index.search({"match_phrase": {"body": "quick brown"}})
        assert results["total_hits"]["value"] == 1
    finally:
        shutil.rmtree(path)


def test_open_existing() -> None:
    """Opening an existing index loads persisted mappings."""
    path, index = make_index()
    try:
        index.add({"title": "persistent doc"})
        index.commit()

        idx2 = luci.Index.open(str(Path(path) / "idx"))
        results = idx2.search({"match": {"title": "persistent"}})
        assert results["total_hits"]["value"] == 1
    finally:
        shutil.rmtree(path)


def test_vector_search() -> None:
    """Hybrid text + vector search returns correct results via RRF."""
    path = tempfile.mkdtemp(prefix="luci_pytest_vec_")
    try:
        mapping = {
            "properties": {
                "title": {"type": "text"},
                "embedding": {"type": "dense_vector", "dims": 4},
            },
        }
        index = luci.Index.create(str(Path(path) / "idx"), mapping)

        index.add({"title": "search", "embedding": [1.0, 0.0, 0.0, 0.0]})
        index.add({"title": "database", "embedding": [0.0, 1.0, 0.0, 0.0]})
        index.add({"title": "cat", "embedding": [0.0, 0.0, 1.0, 0.0]})
        index.commit()

        results = index.search(
            {
                "query": {"match": {"title": "search"}},
                "knn": {
                    "field": "embedding",
                    "query_vector": [0.9, 0.1, 0.0, 0.0],
                    "k": 3,
                    "num_candidates": 5,
                },
                "size": 3,
            },
        )
        assert results["total_hits"]["value"] >= 1
        # First result should be "search" (matches both text and vector)
        assert results["hits"][0]["_doc_id"] == 0
    finally:
        shutil.rmtree(path)
