"""M6 Qdrant 向量存储模块测试。

使用 qdrant-client 内存模式(:memory:),不依赖 Docker。
"""

from __future__ import annotations

import pytest

from vectorstore import (
    SearchFilter,
    SearchResult,
    VectorStore,
    make_qdrant_client,
)
from vectorstore.client import _build_filter

# --------------------------------------------------------------------------- #
# fixtures
# --------------------------------------------------------------------------- #

@pytest.fixture
def store() -> VectorStore:
    c = make_qdrant_client(memory=True)
    s = VectorStore(c, collection_name="test_m6", vector_dim=4)
    s.init_collection()
    yield s
    s.close()


def _point(id_: str, vector: list[float], **payload: object) -> dict:
    return {"id": id_, "vector": vector, "payload": dict(payload)}


# --------------------------------------------------------------------------- #
# Collection 管理
# --------------------------------------------------------------------------- #

def test_init_collection_creates(store: VectorStore) -> None:
    info = store.info()
    assert info.exists is True
    assert info.name == "test_m6"


def test_init_collection_idempotent(store: VectorStore) -> None:
    """再次 init 不应报错,count 不变。"""
    store.upsert([_point("a", [1.0, 0, 0, 0])])
    store.init_collection()  # 不应重建
    assert store.count() == 1


def test_init_collection_recreate_clears(store: VectorStore) -> None:
    store.upsert([_point("a", [1.0, 0, 0, 0])])
    store.init_collection(recreate=True)
    assert store.count() == 0


def test_delete_collection(store: VectorStore) -> None:
    store.delete_collection()
    assert store.info().exists is False
    # 再次 init 应恢复
    store.init_collection()
    assert store.info().exists is True


# --------------------------------------------------------------------------- #
# upsert + count
# --------------------------------------------------------------------------- #

def test_upsert_and_count(store: VectorStore) -> None:
    store.upsert([
        _point("a", [1, 0, 0, 0], title="Article A"),
        _point("b", [0, 1, 0, 0], title="Article B"),
    ])
    assert store.count() == 2


def test_upsert_idempotent(store: VectorStore) -> None:
    """同 url_hash 再次 upsert 不应增加 count,数据被覆盖。"""
    store.upsert([_point("a", [1, 0, 0, 0], title="Old")])
    store.upsert([_point("a", [0, 0, 0, 1], title="New")])
    assert store.count() == 1


# --------------------------------------------------------------------------- #
# query - 语义检索
# --------------------------------------------------------------------------- #

def test_query_returns_score_desc(store: VectorStore) -> None:
    store.upsert([
        _point("a", [1.0, 0, 0, 0], title="A"),
        _point("b", [0.0, 1.0, 0, 0], title="B"),
        _point("c", [0.0, 0, 1.0, 0], title="C"),
    ])
    results = store.query(query_vector=[0.9, 0.1, 0, 0], top_k=2)
    assert len(results) == 2
    assert results[0].url_hash == "a"
    # score 应递减
    assert results[0].score >= results[1].score


def test_query_score_threshold(store: VectorStore) -> None:
    store.upsert([
        _point("a", [1, 0, 0, 0], title="A"),
        _point("b", [0, 1, 0, 0], title="B"),
    ])
    # 只有 a 会匹配
    results = store.query(query_vector=[1, 0, 0, 0], top_k=10, score_threshold=0.9)
    assert len(results) == 1
    assert results[0].url_hash == "a"


# --------------------------------------------------------------------------- #
# query - 结构化过滤
# --------------------------------------------------------------------------- #

def test_query_filter_by_source_id(store: VectorStore) -> None:
    store.upsert([
        _point("a1", [1, 0, 0, 0], source_id="cls", title="CLS article"),
        _point("a2", [0.9, 0.1, 0, 0], source_id="sina", title="Sina article"),
    ])
    results = store.query(
        query_vector=[1, 0, 0, 0],
        filter=SearchFilter(source_id="cls"),
        top_k=5,
    )
    assert len(results) == 1
    assert results[0].source_id == "cls"


def test_query_filter_by_stock_codes(store: VectorStore) -> None:
    store.upsert([
        _point("a", [1, 0, 0, 0], source_id="cls",
               event={"stock_codes": ["300750"], "sentiment": "positive"}),
        _point("b", [0.9, 0.1, 0, 0], source_id="sina",
               event={"stock_codes": ["000001"], "sentiment": "neutral"}),
        _point("c", [0.8, 0.2, 0, 0], source_id="sina",
               event={"stock_codes": ["300750"], "sentiment": "negative"}),
    ])
    results = store.query(
        query_vector=[1, 0, 0, 0],
        filter=SearchFilter(stock_codes=["300750"]),
        top_k=5,
    )
    assert len(results) == 2
    for r in results:
        assert "300750" in (r.event or {}).get("stock_codes", [])


def test_query_filter_by_sentiment(store: VectorStore) -> None:
    store.upsert([
        _point("a", [1, 0, 0, 0], source_id="cls",
               event={"sentiment": "positive"}),
        _point("b", [0, 1, 0, 0], source_id="cls",
               event={"sentiment": "negative"}),
    ])
    results = store.query(
        query_vector=[1, 0, 0, 0],
        filter=SearchFilter(sentiment="positive"),
        top_k=5,
    )
    assert len(results) >= 1
    assert all((r.event or {}).get("sentiment") == "positive" for r in results)


def test_query_filter_by_importance_min(store: VectorStore) -> None:
    store.upsert([
        _point("a", [1, 0, 0, 0], source_id="cls",
               event={"importance": 2}),
        _point("b", [0, 1, 0, 0], source_id="cls",
               event={"importance": 4}),
        _point("c", [0, 0, 1, 0], source_id="cls",
               event={"importance": 5}),
    ])
    results = store.query(
        query_vector=[0.5, 0.5, 0.5, 0],
        filter=SearchFilter(importance_min=4),
        top_k=5,
    )
    assert all((r.event or {}).get("importance", 0) >= 4 for r in results)


def test_query_filter_by_industry(store: VectorStore) -> None:
    store.upsert([
        _point("a", [1, 0, 0, 0], source_id="cls",
               event={"industries": ["动力电池"]}),
        _point("b", [0, 1, 0, 0], source_id="cls",
               event={"industries": ["白酒"]}),
    ])
    results = store.query(
        query_vector=[1, 0, 0, 0],
        filter=SearchFilter(industries=["动力电池"]),
        top_k=5,
    )
    assert len(results) >= 1
    for r in results:
        assert "动力电池" in (r.event or {}).get("industries", [])


# --------------------------------------------------------------------------- #
# Filter 构建
# --------------------------------------------------------------------------- #

def test_build_filter_empty_returns_none() -> None:
    assert _build_filter(SearchFilter()) is None


def test_build_filter_source_id() -> None:
    f = _build_filter(SearchFilter(source_id="cls"))
    assert f is not None and len(f.must) == 1  # type: ignore[arg-type]


def test_build_filter_date_range() -> None:
    f = _build_filter(SearchFilter(publish_date_from="2026-06-01", publish_date_to="2026-06-30"))
    assert f is not None
    # range 应含 gte + lte
    cond = f.must[0]  # type: ignore[union-attr]
    assert cond.key == "publish_time"


# --------------------------------------------------------------------------- #
# search_result 模型
# --------------------------------------------------------------------------- #

def test_search_result_short_summary() -> None:
    r = SearchResult(
        url_hash="abc",
        score=0.95,
        title="宁德时代签约 100GWh 协议",
        url="https://x/1",
        source_id="cls",
        event={"stock_codes": ["300750"], "sentiment": "positive"},
    )
    s = r.short_summary()
    assert "cls" in s and "0.9500" in s and "300750" in s


def test_search_result_handles_none_event() -> None:
    r = SearchResult(
        url_hash="abc", score=0.5, title="t", url="u", source_id="cls", event=None,
    )
    s = r.short_summary()
    assert "-" in s  # 无 stock_code
