from __future__ import annotations

# pyright: reportMissingImports=false

"""
WikiStore 단위 테스트
테스터: 모리건 (개발3팀)
"""

import time

import pytest

from kakao_knowledge.wiki_store import WikiStore


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


@pytest.fixture
def store():
    """인메모리 WikiStore 인스턴스"""
    s = WikiStore(db_path=":memory:")
    yield s
    s.close()


@pytest.fixture
def sample_entry():
    """테스트용 위키 엔트리"""
    return {
        "id": "kakao-001",
        "title": "고지의무 - 주상병 부상병 구분",
        "category": "고지의무",
        "subcategory": "질병고지",
        "question": "주상병은 비염, 부상병은 위보호용 약처방을 위해 위염이 진단된 건데 고지대상인가요?",
        "answer": "부상병으로 기재된 위염도 진단명이 있으면 고지대상입니다.",
        "expert": "강나윤 / 인카 / 수도권",
        "source_date": "2026-01-15",
        "source_chat": "보상스터디",
        "keywords": ["고지의무", "주상병", "부상병", "위염", "비염"],
        "confidence": "medium",
        "raw_thread": ["[강나윤] 질문...", "[답변자] 답변..."],
    }


# ---------------------------------------------------------------------------
# 1. insert_entry + get_entry
# ---------------------------------------------------------------------------


class TestInsertAndGet:
    def test_insert_and_get(self, store: WikiStore, sample_entry: dict):
        """insert_entry 후 get_entry로 조회하면 모든 필드가 일치해야 한다."""
        entry_id = store.insert_entry(sample_entry)
        assert entry_id == sample_entry["id"]

        result = store.get_entry(entry_id)
        assert result is not None
        assert result["id"] == sample_entry["id"]
        assert result["title"] == sample_entry["title"]
        assert result["category"] == sample_entry["category"]
        assert result["subcategory"] == sample_entry["subcategory"]
        assert result["question"] == sample_entry["question"]
        assert result["answer"] == sample_entry["answer"]
        assert result["expert"] == sample_entry["expert"]
        assert result["source_date"] == sample_entry["source_date"]
        assert result["source_chat"] == sample_entry["source_chat"]
        assert result["keywords"] == sample_entry["keywords"]
        assert result["confidence"] == sample_entry["confidence"]
        assert result["raw_thread"] == sample_entry["raw_thread"]

    def test_insert_auto_timestamps(self, store: WikiStore, sample_entry: dict):
        """insert 시 created_at과 updated_at이 자동 설정되어야 한다."""
        store.insert_entry(sample_entry)
        result = store.get_entry(sample_entry["id"])
        assert result is not None
        assert result["created_at"] is not None
        assert result["updated_at"] is not None
        # ISO 형식 문자열인지 확인
        assert "T" in result["created_at"]
        assert "T" in result["updated_at"]

    def test_get_nonexistent(self, store: WikiStore):
        """존재하지 않는 id 조회 시 None을 반환해야 한다."""
        result = store.get_entry("nonexistent-id-9999")
        assert result is None


# ---------------------------------------------------------------------------
# 2. update_entry
# ---------------------------------------------------------------------------


class TestUpdateEntry:
    def test_update_entry(self, store: WikiStore, sample_entry: dict):
        """필드 부분 업데이트 후 변경사항이 반영되고 updated_at이 갱신되어야 한다."""
        store.insert_entry(sample_entry)
        original = store.get_entry(sample_entry["id"])
        assert original is not None
        original_updated_at = original["updated_at"]

        # updated_at 비교를 위해 아주 짧게 대기
        time.sleep(0.01)

        success = store.update_entry(sample_entry["id"], {"answer": "수정된 답변입니다."})
        assert success is True

        updated = store.get_entry(sample_entry["id"])
        assert updated is not None
        assert updated["answer"] == "수정된 답변입니다."
        # 다른 필드는 그대로
        assert updated["title"] == sample_entry["title"]
        # updated_at이 변경되어야 함
        assert updated["updated_at"] >= original_updated_at

    def test_update_nonexistent(self, store: WikiStore):
        """존재하지 않는 id 업데이트 시 False를 반환해야 한다."""
        result = store.update_entry("nonexistent-id-9999", {"answer": "변경"})
        assert result is False


# ---------------------------------------------------------------------------
# 3. delete_entry
# ---------------------------------------------------------------------------


class TestDeleteEntry:
    def test_delete_entry(self, store: WikiStore, sample_entry: dict):
        """삭제 후 get_entry 조회 시 None을 반환해야 한다."""
        store.insert_entry(sample_entry)
        success = store.delete_entry(sample_entry["id"])
        assert success is True
        assert store.get_entry(sample_entry["id"]) is None

    def test_delete_nonexistent(self, store: WikiStore):
        """존재하지 않는 id 삭제 시 False를 반환해야 한다."""
        result = store.delete_entry("nonexistent-id-9999")
        assert result is False


# ---------------------------------------------------------------------------
# 4. list_entries
# ---------------------------------------------------------------------------


class TestListEntries:
    def _make_entries(self) -> list[dict]:
        return [
            {
                "id": "entry-001",
                "title": "고지의무 항목1",
                "category": "고지의무",
                "question": "질문1",
                "answer": "답변1",
                "status": "draft",
            },
            {
                "id": "entry-002",
                "title": "보상 항목1",
                "category": "보상",
                "question": "질문2",
                "answer": "답변2",
                "status": "approved",
            },
            {
                "id": "entry-003",
                "title": "고지의무 항목2",
                "category": "고지의무",
                "question": "질문3",
                "answer": "답변3",
                "status": "rejected",
            },
            {
                "id": "entry-004",
                "title": "보상 항목2",
                "category": "보상",
                "question": "질문4",
                "answer": "답변4",
                "status": "approved",
            },
        ]

    def test_list_entries_all(self, store: WikiStore):
        """여러 항목 삽입 후 전체 목록 조회 시 모두 반환되어야 한다."""
        entries = self._make_entries()
        for e in entries:
            store.insert_entry(e)
        results = store.list_entries()
        assert len(results) == 4

    def test_list_entries_filter_category(self, store: WikiStore):
        """category 필터링 시 해당 카테고리 항목만 반환되어야 한다."""
        for e in self._make_entries():
            store.insert_entry(e)
        results = store.list_entries(category="고지의무")
        assert len(results) == 2
        assert all(r["category"] == "고지의무" for r in results)

    def test_list_entries_filter_status(self, store: WikiStore):
        """status 필터링 시 해당 상태 항목만 반환되어야 한다."""
        for e in self._make_entries():
            store.insert_entry(e)
        results = store.list_entries(status="approved")
        assert len(results) == 2
        assert all(r["status"] == "approved" for r in results)

    def test_list_entries_pagination(self, store: WikiStore):
        """limit/offset 페이지네이션이 정상 동작해야 한다."""
        for e in self._make_entries():
            store.insert_entry(e)
        page1 = store.list_entries(limit=2, offset=0)
        page2 = store.list_entries(limit=2, offset=2)
        assert len(page1) == 2
        assert len(page2) == 2
        # 두 페이지의 id가 겹치지 않아야 한다
        ids_page1 = {r["id"] for r in page1}
        ids_page2 = {r["id"] for r in page2}
        assert ids_page1.isdisjoint(ids_page2)


# ---------------------------------------------------------------------------
# 5. search_entries (FTS5)
# ---------------------------------------------------------------------------


class TestSearchEntries:
    def test_search_entries(self, store: WikiStore, sample_entry: dict):
        """title/question/answer/keywords에서 검색 결과를 반환해야 한다."""
        store.insert_entry(sample_entry)
        results = store.search_entries("위염")
        assert len(results) >= 1
        ids = [r["id"] for r in results]
        assert sample_entry["id"] in ids

    def test_search_no_results(self, store: WikiStore, sample_entry: dict):
        """검색어와 매칭되는 항목이 없으면 빈 리스트를 반환해야 한다."""
        store.insert_entry(sample_entry)
        results = store.search_entries("존재하지않는검색어XYZ999")
        assert results == []


# ---------------------------------------------------------------------------
# 6. approve_entry / reject_entry
# ---------------------------------------------------------------------------


class TestApproveReject:
    def test_approve_entry(self, store: WikiStore, sample_entry: dict):
        """approve 후 status가 'approved'가 되어야 한다."""
        store.insert_entry(sample_entry)
        ok = store.approve_entry(sample_entry["id"])
        assert ok is True
        result = store.get_entry(sample_entry["id"])
        assert result is not None
        assert result["status"] == "approved"

    def test_reject_entry(self, store: WikiStore, sample_entry: dict):
        """reject 후 status가 'rejected'가 되어야 한다."""
        store.insert_entry(sample_entry)
        ok = store.reject_entry(sample_entry["id"])
        assert ok is True
        result = store.get_entry(sample_entry["id"])
        assert result is not None
        assert result["status"] == "rejected"


# ---------------------------------------------------------------------------
# 7. bulk_import
# ---------------------------------------------------------------------------


class TestBulkImport:
    def test_bulk_import(self, store: WikiStore):
        """여러 항목 일괄 import 시 성공 건수가 반환되어야 한다."""
        entries = [
            {"id": f"bulk-{i:03d}", "title": f"항목{i}", "category": "테스트", "question": f"q{i}", "answer": f"a{i}"}
            for i in range(5)
        ]
        count = store.bulk_import(entries)
        assert count == 5
        # 실제로 저장되었는지 확인
        for e in entries:
            assert store.get_entry(e["id"]) is not None

    def test_bulk_import_keywords_list(self, store: WikiStore):
        """keywords가 list인 경우 DB에 JSON string으로 저장되고, 조회 시 다시 list로 복원되어야 한다."""
        entry = {
            "id": "bulk-keywords-001",
            "title": "키워드 테스트",
            "category": "테스트",
            "question": "질문",
            "answer": "답변",
            "keywords": ["키워드1", "키워드2", "키워드3"],
        }
        count = store.bulk_import([entry])
        assert count == 1
        result = store.get_entry("bulk-keywords-001")
        assert result is not None
        assert isinstance(result["keywords"], list)
        assert result["keywords"] == ["키워드1", "키워드2", "키워드3"]


# ---------------------------------------------------------------------------
# 8. get_stats
# ---------------------------------------------------------------------------


class TestGetStats:
    def test_get_stats(self, store: WikiStore):
        """카테고리별/상태별 통계가 정확하게 반환되어야 한다."""
        entries = [
            {"id": "stat-001", "title": "t1", "category": "고지의무", "question": "q", "answer": "a", "status": "draft"},
            {"id": "stat-002", "title": "t2", "category": "고지의무", "question": "q", "answer": "a", "status": "approved"},
            {"id": "stat-003", "title": "t3", "category": "보상", "question": "q", "answer": "a", "status": "approved"},
            {"id": "stat-004", "title": "t4", "category": "보상", "question": "q", "answer": "a", "status": "rejected"},
            {"id": "stat-005", "title": "t5", "category": "보상", "question": "q", "answer": "a", "status": "draft"},
        ]
        for e in entries:
            store.insert_entry(e)

        stats = store.get_stats()
        assert stats["total"] == 5

        by_category = stats["by_category"]
        assert by_category["고지의무"] == 2
        assert by_category["보상"] == 3

        by_status = stats["by_status"]
        assert by_status["draft"] == 2
        assert by_status["approved"] == 2
        assert by_status["rejected"] == 1
