# pyright: reportMissingImports=false
"""VectorStore 테스트 — TDD Phase 3"""
from __future__ import annotations

import pytest

from kakao_knowledge.vector_store import VectorStore


# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------


@pytest.fixture
def store(tmp_path) -> VectorStore:
    """임시 경로에 VectorStore 생성."""
    return VectorStore(db_path=str(tmp_path / "chroma_db"), collection_name="test_insights")


@pytest.fixture
def sample_insights() -> list[dict]:
    """InsightV2 형식의 테스트 데이터 5개."""
    return [
        {
            "id": "insight-001",
            "title": "광응고술 보상 기준",
            "type": "qa",
            "category": "보상/일반",
            "summary": "광응고술은 실손보험에서 수술비로 인정되며 입원 기준 적용된다.",
            "key_points": ["광응고술은 수술비 대상", "입원 없이도 수술비 청구 가능"],
            "expert": "김전문",
            "confidence": "high",
            "related_topics": ["광응고술", "실손보험", "수술비"],
            "tags": ["#보상", "#실손"],
            "source_date": "2025-12-01",
            "source_chat": "보험전문가방",
            "participants": ["김전문", "이설계"],
        },
        {
            "id": "insight-002",
            "title": "고지의무 위반 시 계약 해지",
            "type": "regulation_interpretation",
            "category": "고지의무",
            "summary": "고지의무 위반이 있더라도 보험사가 2년 내 발견하지 못하면 해지 불가.",
            "key_points": ["2년 경과 후 해지 불가", "고의 위반은 예외"],
            "expert": "박약관",
            "confidence": "high",
            "related_topics": ["고지의무", "계약해지", "약관"],
            "tags": ["#고지의무", "#약관"],
            "source_date": "2025-12-02",
            "source_chat": "보험전문가방",
            "participants": ["박약관", "최신입"],
        },
        {
            "id": "insight-003",
            "title": "자동차보험 렌트비 청구 방법",
            "type": "practical_tip",
            "category": "보상/자동차",
            "summary": "사고 후 렌트비는 수리 기간 동안 인정되며 사전 승인이 필요하다.",
            "key_points": ["수리 기간 렌트비 인정", "사전 보험사 승인 필수"],
            "expert": "홍자동차",
            "confidence": "medium",
            "related_topics": ["자동차보험", "렌트비", "대물"],
            "tags": ["#자동차", "#렌트"],
            "source_date": "2025-12-03",
            "source_chat": "보험전문가방",
            "participants": ["홍자동차", "정설계"],
        },
        {
            "id": "insight-004",
            "title": "실손보험 비급여 청구 절차",
            "type": "qa",
            "category": "보상/일반",
            "summary": "비급여 항목은 영수증과 세부 내역서가 필요하며 온라인 청구 가능.",
            "key_points": ["영수증 + 세부 내역서 필수", "앱으로 청구 가능"],
            "expert": "이실손",
            "confidence": "medium",
            "related_topics": ["실손보험", "비급여", "청구"],
            "tags": ["#실손", "#청구"],
            "source_date": "2025-12-04",
            "source_chat": "보험전문가방",
            "participants": ["이실손", "김신청"],
        },
        {
            "id": "insight-005",
            "title": "종신보험 해약환급금 계산",
            "type": "expert_opinion",
            "category": "보상/장기",
            "summary": "해약환급금은 납입 기간과 경과 년수에 따라 크게 차이난다.",
            "key_points": ["초기 해약 시 원금 손실", "10년 이후 환급률 개선"],
            "expert": "최종신",
            "confidence": "low",
            "related_topics": ["종신보험", "해약환급금", "장기"],
            "tags": ["#종신", "#환급금"],
            "source_date": "2025-12-05",
            "source_chat": "보험전문가방",
            "participants": ["최종신", "박고객"],
        },
    ]


# ---------------------------------------------------------------------------
# Tests
# ---------------------------------------------------------------------------


class TestVectorStore:
    def test_empty_store_count_zero(self, store: VectorStore) -> None:
        """빈 스토어의 count는 0이어야 한다."""
        assert store.count() == 0

    def test_empty_store_search(self, store: VectorStore) -> None:
        """빈 스토어에서 검색하면 빈 리스트를 반환해야 한다."""
        results = store.search_similar("보험 청구")
        assert isinstance(results, list)
        assert len(results) == 0

    def test_add_insights_returns_count(self, store: VectorStore, sample_insights: list[dict]) -> None:
        """add_insights는 추가된 수를 반환해야 한다."""
        count = store.add_insights(sample_insights)
        assert count == len(sample_insights)

    def test_count_after_add(self, store: VectorStore, sample_insights: list[dict]) -> None:
        """add_insights 후 count가 올바른 수를 반환해야 한다."""
        store.add_insights(sample_insights)
        assert store.count() == len(sample_insights)

    def test_search_similar_returns_list(self, store: VectorStore, sample_insights: list[dict]) -> None:
        """search_similar는 리스트를 반환해야 한다."""
        store.add_insights(sample_insights)
        results = store.search_similar("보험 청구")
        assert isinstance(results, list)

    def test_search_similar_relevant_results(self, store: VectorStore, sample_insights: list[dict]) -> None:
        """search_similar는 관련 인사이트를 반환해야 한다."""
        store.add_insights(sample_insights)
        results = store.search_similar("광응고술 수술비 청구", top_k=3)
        assert len(results) <= 3
        # 결과에 필수 키가 있어야 한다
        for r in results:
            assert "id" in r
            assert "title" in r
            assert "category" in r
            assert "distance" in r
            assert "summary" in r

    def test_search_similar_top_k(self, store: VectorStore, sample_insights: list[dict]) -> None:
        """top_k 파라미터가 결과 수를 제한해야 한다."""
        store.add_insights(sample_insights)
        results = store.search_similar("보험", top_k=2)
        assert len(results) <= 2

    def test_get_by_id(self, store: VectorStore, sample_insights: list[dict]) -> None:
        """get_by_id는 해당 인사이트를 반환해야 한다."""
        store.add_insights(sample_insights)
        result = store.get_by_id("insight-001")
        assert result is not None
        assert result["id"] == "insight-001"

    def test_get_by_id_not_found(self, store: VectorStore) -> None:
        """존재하지 않는 ID 조회 시 None을 반환해야 한다."""
        result = store.get_by_id("nonexistent-id")
        assert result is None

    def test_delete_by_id(self, store: VectorStore, sample_insights: list[dict]) -> None:
        """delete_by_id는 인사이트를 삭제하고 True를 반환해야 한다."""
        store.add_insights(sample_insights)
        before_count = store.count()
        result = store.delete_by_id("insight-001")
        assert result is True
        assert store.count() == before_count - 1
        assert store.get_by_id("insight-001") is None

    def test_delete_nonexistent_returns_false(self, store: VectorStore) -> None:
        """존재하지 않는 ID 삭제 시 False를 반환해야 한다."""
        result = store.delete_by_id("nonexistent-id")
        assert result is False

    def test_upsert_existing(self, store: VectorStore, sample_insights: list[dict]) -> None:
        """이미 존재하는 ID는 업데이트(upsert)해야 한다."""
        store.add_insights(sample_insights)
        count_before = store.count()

        # 동일한 ID로 수정된 데이터 추가
        modified = [{**sample_insights[0], "title": "수정된 광응고술 보상 기준"}]
        store.add_insights(modified)

        # count는 변하지 않아야 한다
        assert store.count() == count_before

        # 내용이 업데이트되어야 한다
        result = store.get_by_id("insight-001")
        assert result is not None
        assert result["title"] == "수정된 광응고술 보상 기준"
