"""
Tests for Knowledge Base manager.

Tests cover:
- CRUD operations (add, get, update, delete, list)
- Search functionality (keyword, category, tag filtering)
- ID generation
- YAML persistence
- Cache invalidation
- Error handling
"""

from datetime import datetime
from pathlib import Path

import pytest

from clauxton.core.knowledge_base import KnowledgeBase
from clauxton.core.models import DuplicateError, KnowledgeBaseEntry, NotFoundError

# ============================================================================
# Fixtures
# ============================================================================


@pytest.fixture
def kb(tmp_path: Path) -> KnowledgeBase:
    """Create a KnowledgeBase instance for testing."""
    return KnowledgeBase(tmp_path)


@pytest.fixture
def sample_entry() -> KnowledgeBaseEntry:
    """Create a sample Knowledge Base entry."""
    return KnowledgeBaseEntry(
        id="KB-20251019-001",
        title="Use FastAPI framework",
        category="architecture",
        content="All backend APIs should use FastAPI framework for consistency.",
        tags=["backend", "api", "fastapi"],
        created_at=datetime(2025, 10, 19, 10, 0, 0),
        updated_at=datetime(2025, 10, 19, 10, 0, 0),
    )


@pytest.fixture
def sample_entries() -> list[KnowledgeBaseEntry]:
    """Create multiple sample entries for testing."""
    return [
        KnowledgeBaseEntry(
            id="KB-20251019-001",
            title="Use FastAPI framework",
            category="architecture",
            content="All backend APIs should use FastAPI framework.",
            tags=["backend", "api"],
            created_at=datetime(2025, 10, 19, 10, 0, 0),
            updated_at=datetime(2025, 10, 19, 10, 0, 0),
        ),
        KnowledgeBaseEntry(
            id="KB-20251019-002",
            title="Write tests first",
            category="convention",
            content="Follow TDD: write tests before implementation.",
            tags=["testing", "tdd"],
            created_at=datetime(2025, 10, 19, 11, 0, 0),
            updated_at=datetime(2025, 10, 19, 11, 0, 0),
        ),
        KnowledgeBaseEntry(
            id="KB-20251019-003",
            title="Use PostgreSQL for production",
            category="decision",
            content="Production database is PostgreSQL 15+.",
            tags=["database", "postgresql"],
            created_at=datetime(2025, 10, 19, 12, 0, 0),
            updated_at=datetime(2025, 10, 19, 12, 0, 0),
        ),
    ]


# ============================================================================
# Initialization Tests
# ============================================================================


def test_kb_initialization(tmp_path: Path) -> None:
    """Test Knowledge Base initialization creates necessary files."""
    kb = KnowledgeBase(tmp_path)

    assert kb.root_dir == tmp_path
    assert kb.kb_file.exists()
    assert kb.kb_file.name == "knowledge-base.yml"
    assert (tmp_path / ".clauxton").exists()


def test_kb_file_permissions(tmp_path: Path) -> None:
    """Test that KB file has secure permissions (600)."""
    kb = KnowledgeBase(tmp_path)

    # Check file permissions (should be 600)
    import os
    perms = oct(os.stat(kb.kb_file).st_mode)[-3:]
    assert perms == "600"


# ============================================================================
# Add Entry Tests
# ============================================================================


def test_add_entry(kb: KnowledgeBase, sample_entry: KnowledgeBaseEntry) -> None:
    """Test adding a valid entry."""
    entry_id = kb.add(sample_entry)

    assert entry_id == "KB-20251019-001"

    # Verify entry was added
    retrieved = kb.get(entry_id)
    assert retrieved.title == "Use FastAPI framework"
    assert retrieved.category == "architecture"


def test_add_duplicate_entry(
    kb: KnowledgeBase, sample_entry: KnowledgeBaseEntry
) -> None:
    """Test that adding duplicate ID raises DuplicateError."""
    kb.add(sample_entry)

    with pytest.raises(DuplicateError) as exc_info:
        kb.add(sample_entry)

    assert "KB-20251019-001" in str(exc_info.value)
    assert "already exists" in str(exc_info.value)


def test_add_multiple_entries(
    kb: KnowledgeBase, sample_entries: list[KnowledgeBaseEntry]
) -> None:
    """Test adding multiple entries."""
    for entry in sample_entries:
        kb.add(entry)

    all_entries = kb.list_all()
    assert len(all_entries) == 3
    assert all_entries[0].id == "KB-20251019-001"
    assert all_entries[2].id == "KB-20251019-003"


# ============================================================================
# Get Entry Tests
# ============================================================================


def test_get_entry(kb: KnowledgeBase, sample_entry: KnowledgeBaseEntry) -> None:
    """Test retrieving an entry by ID."""
    kb.add(sample_entry)

    retrieved = kb.get("KB-20251019-001")

    assert retrieved.id == "KB-20251019-001"
    assert retrieved.title == "Use FastAPI framework"
    assert retrieved.tags == ["backend", "api", "fastapi"]


def test_get_nonexistent_entry(kb: KnowledgeBase) -> None:
    """Test that getting nonexistent entry raises NotFoundError."""
    with pytest.raises(NotFoundError) as exc_info:
        kb.get("KB-99999999-999")

    assert "KB-99999999-999" in str(exc_info.value)
    assert "not found" in str(exc_info.value)


# ============================================================================
# Search Tests
# ============================================================================


def test_search_by_keyword(
    kb: KnowledgeBase, sample_entries: list[KnowledgeBaseEntry]
) -> None:
    """Test searching by keyword in title/content."""
    for entry in sample_entries:
        kb.add(entry)

    # Search for "FastAPI"
    results = kb.search("FastAPI")
    assert len(results) == 1
    assert results[0].title == "Use FastAPI framework"

    # Search for "tests"
    results = kb.search("tests")
    assert len(results) == 1
    assert results[0].title == "Write tests first"


def test_search_case_insensitive(
    kb: KnowledgeBase, sample_entries: list[KnowledgeBaseEntry]
) -> None:
    """Test that search is case-insensitive."""
    for entry in sample_entries:
        kb.add(entry)

    # Search with different cases
    results_lower = kb.search("fastapi")
    results_upper = kb.search("FASTAPI")
    results_mixed = kb.search("FastAPI")

    assert len(results_lower) == 1
    assert len(results_upper) == 1
    assert len(results_mixed) == 1
    assert results_lower[0].id == results_upper[0].id == results_mixed[0].id


def test_search_with_category_filter(
    kb: KnowledgeBase, sample_entries: list[KnowledgeBaseEntry]
) -> None:
    """Test searching with category filter."""
    for entry in sample_entries:
        kb.add(entry)

    # Search for "database" in decision category
    results = kb.search("database", category="decision")
    assert len(results) == 1
    assert results[0].category == "decision"

    # Search in wrong category should return nothing
    results = kb.search("database", category="architecture")
    assert len(results) == 0


def test_search_with_tag_filter(
    kb: KnowledgeBase, sample_entries: list[KnowledgeBaseEntry]
) -> None:
    """Test searching with tag filter."""
    for entry in sample_entries:
        kb.add(entry)

    # Search with tag filter
    results = kb.search("production", tags=["database"])
    assert len(results) == 1
    assert "database" in results[0].tags


def test_search_limit(
    kb: KnowledgeBase, sample_entries: list[KnowledgeBaseEntry]
) -> None:
    """Test that search respects limit parameter."""
    # Add entries
    for entry in sample_entries:
        kb.add(entry)

    # Search with limit=1
    results = kb.search("", limit=1)
    assert len(results) <= 1


def test_search_no_results(kb: KnowledgeBase) -> None:
    """Test that search returns empty list when no matches."""
    results = kb.search("nonexistent")
    assert results == []


def test_search_relevance_ranking(
    kb: KnowledgeBase, sample_entries: list[KnowledgeBaseEntry]
) -> None:
    """Test that search results are ranked by relevance."""
    for entry in sample_entries:
        kb.add(entry)

    # "API" appears in title of first entry (higher weight)
    # and in tags, so it should rank higher
    results = kb.search("api")

    # First result should be the one with "API" in title
    assert len(results) >= 1
    assert "api" in results[0].title.lower() or "api" in results[0].tags


# ============================================================================
# Update Entry Tests
# ============================================================================


def test_update_entry(kb: KnowledgeBase, sample_entry: KnowledgeBaseEntry) -> None:
    """Test updating an entry."""
    kb.add(sample_entry)

    updated = kb.update(
        "KB-20251019-001",
        {
            "content": "Updated: Use FastAPI 0.100+ for all APIs.",
            "tags": ["backend", "api", "fastapi", "updated"],
        },
    )

    assert updated.content == "Updated: Use FastAPI 0.100+ for all APIs."
    assert "updated" in updated.tags
    assert updated.version == 2

    # Verify persistence
    retrieved = kb.get("KB-20251019-001")
    assert retrieved.content == updated.content
    assert retrieved.version == 2


def test_update_nonexistent_entry(kb: KnowledgeBase) -> None:
    """Test that updating nonexistent entry raises NotFoundError."""
    with pytest.raises(NotFoundError):
        kb.update("KB-99999999-999", {"content": "New content"})


def test_update_increments_version(
    kb: KnowledgeBase, sample_entry: KnowledgeBaseEntry
) -> None:
    """Test that updates increment version number."""
    kb.add(sample_entry)
    assert sample_entry.version == 1

    updated1 = kb.update("KB-20251019-001", {"content": "Update 1"})
    assert updated1.version == 2

    updated2 = kb.update("KB-20251019-001", {"content": "Update 2"})
    assert updated2.version == 3


def test_update_preserves_immutable_fields(
    kb: KnowledgeBase, sample_entry: KnowledgeBaseEntry
) -> None:
    """Test that update doesn't modify immutable fields (id, created_at)."""
    kb.add(sample_entry)
    original_created_at = sample_entry.created_at

    # Try to update immutable fields (should be ignored)
    updated = kb.update(
        "KB-20251019-001",
        {
            "id": "KB-99999999-999",  # Should be ignored
            "created_at": datetime(2020, 1, 1),  # Should be ignored
            "content": "New content",
        },
    )

    assert updated.id == "KB-20251019-001"  # ID unchanged
    assert updated.created_at == original_created_at  # created_at unchanged
    assert updated.content == "New content"  # content updated


# ============================================================================
# Delete Entry Tests
# ============================================================================


def test_delete_entry(kb: KnowledgeBase, sample_entry: KnowledgeBaseEntry) -> None:
    """Test deleting an entry."""
    kb.add(sample_entry)
    assert len(kb.list_all()) == 1

    kb.delete("KB-20251019-001")

    assert len(kb.list_all()) == 0
    with pytest.raises(NotFoundError):
        kb.get("KB-20251019-001")


def test_delete_nonexistent_entry(kb: KnowledgeBase) -> None:
    """Test that deleting nonexistent entry raises NotFoundError."""
    with pytest.raises(NotFoundError):
        kb.delete("KB-99999999-999")


# ============================================================================
# List All Tests
# ============================================================================


def test_list_all_empty(kb: KnowledgeBase) -> None:
    """Test listing all entries when KB is empty."""
    entries = kb.list_all()
    assert entries == []


def test_list_all_with_entries(
    kb: KnowledgeBase, sample_entries: list[KnowledgeBaseEntry]
) -> None:
    """Test listing all entries."""
    for entry in sample_entries:
        kb.add(entry)

    all_entries = kb.list_all()
    assert len(all_entries) == 3
    assert all_entries[0].id == "KB-20251019-001"
    assert all_entries[1].id == "KB-20251019-002"
    assert all_entries[2].id == "KB-20251019-003"


# ============================================================================
# ID Generation Tests
# ============================================================================


def test_generate_id(kb: KnowledgeBase) -> None:
    """Test ID generation."""
    entry_id = kb._generate_id()

    # Check format: KB-YYYYMMDD-NNN
    assert entry_id.startswith("KB-")
    parts = entry_id.split("-")
    assert len(parts) == 3
    assert len(parts[1]) == 8  # YYYYMMDD
    assert len(parts[2]) == 3  # NNN
    assert parts[2].isdigit()


def test_generate_id_sequential(kb: KnowledgeBase) -> None:
    """Test that generated IDs are sequential for the same day."""
    id1 = kb._generate_id()
    id2 = kb._generate_id()

    # Extract sequence numbers
    seq1 = int(id1.split("-")[-1])
    seq2 = int(id2.split("-")[-1])

    # Second ID should be sequential (even though no entry was added)
    # Note: This depends on implementation - current impl checks existing entries
    # So if no entries exist, both will return 001
    assert seq1 == 1
    assert seq2 == 1  # No entry added, so still 001


# ============================================================================
# Persistence Tests
# ============================================================================


def test_persistence_after_restart(
    tmp_path: Path, sample_entry: KnowledgeBaseEntry
) -> None:
    """Test that entries persist after KB restart."""
    # Create KB and add entry
    kb1 = KnowledgeBase(tmp_path)
    kb1.add(sample_entry)

    # Create new KB instance (simulates restart)
    kb2 = KnowledgeBase(tmp_path)
    retrieved = kb2.get("KB-20251019-001")

    assert retrieved.title == sample_entry.title
    assert retrieved.content == sample_entry.content


def test_yaml_file_structure(
    tmp_path: Path, sample_entry: KnowledgeBaseEntry
) -> None:
    """Test that YAML file has correct structure."""
    kb = KnowledgeBase(tmp_path)
    kb.add(sample_entry)

    # Read YAML file directly
    from clauxton.utils.yaml_utils import read_yaml

    data = read_yaml(kb.kb_file)

    assert "version" in data
    assert "project_name" in data
    assert "entries" in data
    assert isinstance(data["entries"], list)
    assert len(data["entries"]) == 1
    assert data["entries"][0]["id"] == "KB-20251019-001"


# ============================================================================
# Cache Tests
# ============================================================================


def test_cache_invalidation_on_add(
    kb: KnowledgeBase, sample_entry: KnowledgeBaseEntry
) -> None:
    """Test that cache is invalidated when entry is added."""
    # Load entries (populates cache)
    kb.list_all()
    assert kb._entries_cache is not None

    # Add entry (should invalidate cache)
    kb.add(sample_entry)
    assert kb._entries_cache is None


def test_cache_invalidation_on_update(
    kb: KnowledgeBase, sample_entry: KnowledgeBaseEntry
) -> None:
    """Test that cache is invalidated when entry is updated."""
    kb.add(sample_entry)
    kb.list_all()  # Populate cache
    assert kb._entries_cache is not None

    kb.update("KB-20251019-001", {"content": "Updated"})
    assert kb._entries_cache is None


def test_cache_invalidation_on_delete(
    kb: KnowledgeBase, sample_entry: KnowledgeBaseEntry
) -> None:
    """Test that cache is invalidated when entry is deleted."""
    kb.add(sample_entry)
    kb.list_all()  # Populate cache
    assert kb._entries_cache is not None

    kb.delete("KB-20251019-001")
    assert kb._entries_cache is None


# ============================================================================
# Additional Edge Case Tests
# ============================================================================


def test_search_empty_query(
    kb: KnowledgeBase, sample_entries: list[KnowledgeBaseEntry]
) -> None:
    """Test searching with empty query returns empty results."""
    for entry in sample_entries:
        kb.add(entry)

    # Empty query should return no results
    results = kb.search("")
    assert results == []


def test_search_special_characters(
    kb: KnowledgeBase, sample_entry: KnowledgeBaseEntry
) -> None:
    """Test searching with special characters in query."""
    kb.add(sample_entry)

    # Special characters in query should be handled safely
    results = kb.search("API+")
    # Should not crash, may or may not find results
    assert isinstance(results, list)


def test_search_long_content(kb: KnowledgeBase) -> None:
    """Test searching in entries with very long content (10000 chars)."""
    # Create more realistic long content with varied words (TF-IDF friendly)
    repeated_text = ("This is a long content entry. " * 300)  # ~9000 chars
    needle_phrase = "The needle keyword appears here in the long content. "
    long_content = repeated_text + needle_phrase + ("More text. " * 10)

    entry = KnowledgeBaseEntry(
        id="KB-20251019-001",
        title="Long content entry",
        category="architecture",
        content=long_content,
        tags=["test"],
        created_at=datetime.now(),
        updated_at=datetime.now(),
    )
    kb.add(entry)

    # Should find "needle" in long content
    results = kb.search("needle")
    assert len(results) >= 1  # Should find at least one result
    assert any(r.id == "KB-20251019-001" for r in results)


def test_yaml_file_human_readable(
    tmp_path: Path, sample_entry: KnowledgeBaseEntry
) -> None:
    """Test that YAML file is human-readable and properly formatted."""
    kb = KnowledgeBase(tmp_path)
    kb.add(sample_entry)

    # Read YAML file as text
    yaml_content = kb.kb_file.read_text()

    # Should contain human-readable fields
    assert "version:" in yaml_content
    assert "project_name:" in yaml_content
    assert "entries:" in yaml_content
    assert "KB-20251019-001" in yaml_content
    assert "Use FastAPI framework" in yaml_content

    # Should not contain Python object representations
    assert "KnowledgeBaseEntry" not in yaml_content
    assert "<" not in yaml_content or ">" not in yaml_content  # No <object> tags


# ============================================================================
# Simple Search Fallback Tests (for when TF-IDF unavailable)
# ============================================================================


def test_simple_search_keyword_matching(kb: KnowledgeBase) -> None:
    """Test _simple_search method directly for keyword matching."""
    # Add test entries with known content
    entry1 = KnowledgeBaseEntry(
        id="KB-20251019-001",
        title="FastAPI framework guide",
        category="architecture",
        content="Use FastAPI for all backend APIs.",
        tags=["backend", "api"],
        created_at=datetime.now(),
        updated_at=datetime.now(),
    )
    entry2 = KnowledgeBaseEntry(
        id="KB-20251019-002",
        title="PostgreSQL database",
        category="decision",
        content="Use PostgreSQL for production.",
        tags=["database"],
        created_at=datetime.now(),
        updated_at=datetime.now(),
    )
    entry3 = KnowledgeBaseEntry(
        id="KB-20251019-003",
        title="API Gateway pattern",
        category="pattern",
        content="Use API Gateway for routing.",
        tags=["api", "gateway"],
        created_at=datetime.now(),
        updated_at=datetime.now(),
    )

    kb.add(entry1)
    kb.add(entry2)
    kb.add(entry3)

    # Test title match (weight 2.0)
    results = kb._simple_search("FastAPI", limit=10)
    assert len(results) >= 1
    assert results[0].id == "KB-20251019-001"

    # Test content match (weight 1.0)
    results = kb._simple_search("PostgreSQL", limit=10)
    assert len(results) >= 1
    assert results[0].id == "KB-20251019-002"

    # Test tag match (weight 1.5)
    results = kb._simple_search("gateway", limit=10)
    assert len(results) >= 1
    assert results[0].id == "KB-20251019-003"


def test_simple_search_relevance_scoring(kb: KnowledgeBase) -> None:
    """Test _simple_search relevance scoring with multiple matches."""
    # Entry with title match (score: 2.0)
    entry1 = KnowledgeBaseEntry(
        id="KB-20251019-001",
        title="API documentation",
        category="architecture",
        content="Some content here.",
        tags=["docs"],
        created_at=datetime.now(),
        updated_at=datetime.now(),
    )
    # Entry with content match only (score: 1.0)
    entry2 = KnowledgeBaseEntry(
        id="KB-20251019-002",
        title="Other topic",
        category="architecture",
        content="API is mentioned in content.",
        tags=["other"],
        created_at=datetime.now(),
        updated_at=datetime.now(),
    )
    # Entry with tag match (score: 1.5)
    entry3 = KnowledgeBaseEntry(
        id="KB-20251019-003",
        title="Different topic",
        category="architecture",
        content="Different content.",
        tags=["api"],
        created_at=datetime.now(),
        updated_at=datetime.now(),
    )

    kb.add(entry1)
    kb.add(entry2)
    kb.add(entry3)

    results = kb._simple_search("API", limit=10)

    # All should match
    assert len(results) == 3

    # Results should be sorted by relevance
    # Entry 1 (title match, score 2.0) should be first
    assert results[0].id == "KB-20251019-001"
    # Entry 3 (tag match, score 1.5) should be second
    assert results[1].id == "KB-20251019-003"
    # Entry 2 (content match, score 1.0) should be third
    assert results[2].id == "KB-20251019-002"


def test_simple_search_with_category_filter(kb: KnowledgeBase) -> None:
    """Test _simple_search with category filter."""
    entry1 = KnowledgeBaseEntry(
        id="KB-20251019-001",
        title="Database decision",
        category="decision",
        content="Use PostgreSQL.",
        tags=["database"],
        created_at=datetime.now(),
        updated_at=datetime.now(),
    )
    entry2 = KnowledgeBaseEntry(
        id="KB-20251019-002",
        title="Database pattern",
        category="pattern",
        content="Use Repository pattern.",
        tags=["database"],
        created_at=datetime.now(),
        updated_at=datetime.now(),
    )

    kb.add(entry1)
    kb.add(entry2)

    # Filter by category
    results = kb._simple_search("database", category="decision", limit=10)

    assert len(results) == 1
    assert results[0].id == "KB-20251019-001"


def test_simple_search_with_tag_filter(kb: KnowledgeBase) -> None:
    """Test _simple_search with tag filter."""
    entry1 = KnowledgeBaseEntry(
        id="KB-20251019-001",
        title="Backend API",
        category="architecture",
        content="FastAPI framework.",
        tags=["backend", "api"],
        created_at=datetime.now(),
        updated_at=datetime.now(),
    )
    entry2 = KnowledgeBaseEntry(
        id="KB-20251019-002",
        title="Frontend API",
        category="architecture",
        content="REST API calls.",
        tags=["frontend", "api"],
        created_at=datetime.now(),
        updated_at=datetime.now(),
    )

    kb.add(entry1)
    kb.add(entry2)

    # Filter by tag
    results = kb._simple_search("API", tags=["backend"], limit=10)

    assert len(results) == 1
    assert results[0].id == "KB-20251019-001"


def test_simple_search_empty_query(kb: KnowledgeBase) -> None:
    """Test _simple_search with empty query."""
    entry = KnowledgeBaseEntry(
        id="KB-20251019-001",
        title="Test entry",
        category="architecture",
        content="Test content.",
        tags=["test"],
        created_at=datetime.now(),
        updated_at=datetime.now(),
    )
    kb.add(entry)

    # Empty query should return empty list
    results = kb._simple_search("", limit=10)
    assert len(results) == 0

    # Whitespace-only query should also return empty
    results = kb._simple_search("   ", limit=10)
    assert len(results) == 0


def test_simple_search_case_insensitive(kb: KnowledgeBase) -> None:
    """Test that _simple_search is case-insensitive."""
    entry = KnowledgeBaseEntry(
        id="KB-20251019-001",
        title="FastAPI Framework",
        category="architecture",
        content="Use FastAPI for APIs.",
        tags=["FastAPI"],
        created_at=datetime.now(),
        updated_at=datetime.now(),
    )
    kb.add(entry)

    # All case variations should find the entry
    assert len(kb._simple_search("fastapi", limit=10)) >= 1
    assert len(kb._simple_search("FASTAPI", limit=10)) >= 1
    assert len(kb._simple_search("FastAPI", limit=10)) >= 1


def test_simple_search_limit(kb: KnowledgeBase) -> None:
    """Test _simple_search respects limit parameter."""
    # Add 5 entries with same keyword
    for i in range(5):
        entry = KnowledgeBaseEntry(
            id=f"KB-20251019-{i+1:03d}",
            title=f"API entry {i+1}",
            category="architecture",
            content="API content.",
            tags=["api"],
            created_at=datetime.now(),
            updated_at=datetime.now(),
        )
        kb.add(entry)

    # Limit should work
    results = kb._simple_search("API", limit=3)
    assert len(results) == 3

    results = kb._simple_search("API", limit=10)
    assert len(results) == 5  # All 5 entries


def test_fallback_to_simple_search_when_tfidf_unavailable(tmp_path: Path) -> None:
    """Test that search falls back to _simple_search when TF-IDF unavailable."""
    import sys
    from unittest.mock import patch

    # Create KB with mocked SEARCH_ENGINE_AVAILABLE = False
    with patch.dict(sys.modules, {'clauxton.core.search': None}):
        # Force reimport of knowledge_base module with SearchEngine unavailable
        from clauxton.core import knowledge_base as kb_module

        # Temporarily set SEARCH_ENGINE_AVAILABLE to False
        original_available = kb_module.SEARCH_ENGINE_AVAILABLE
        kb_module.SEARCH_ENGINE_AVAILABLE = False

        try:
            # Create new KB instance (should not initialize SearchEngine)
            kb = KnowledgeBase(tmp_path)
            assert kb._search_engine is None

            # Add entry
            entry = KnowledgeBaseEntry(
                id="KB-20251019-001",
                title="Test entry",
                category="architecture",
                content="Test content with keyword.",
                tags=["test"],
                created_at=datetime.now(),
                updated_at=datetime.now(),
            )
            kb.add(entry)

            # Search should use _simple_search fallback
            results = kb.search("keyword")
            assert len(results) >= 1
            assert results[0].id == "KB-20251019-001"

        finally:
            # Restore original value
            kb_module.SEARCH_ENGINE_AVAILABLE = original_available


# ============================================================================
# Export/Import Tests (Added for 80%+ coverage)
# ============================================================================


def test_export_to_docs_empty_kb(tmp_path: Path) -> None:
    """Test exporting empty KB to docs."""
    kb = KnowledgeBase(tmp_path)
    export_dir = tmp_path / "docs"

    # Export empty KB
    kb.export_to_markdown(export_dir)

    # Export dir should be created
    assert export_dir.exists()
    assert export_dir.is_dir()

    # Should have minimal or no markdown files
    md_files = list(export_dir.glob("*.md"))
    # Empty KB may create index or may be empty
    assert len(md_files) >= 0


def test_export_to_docs_with_entries(tmp_path: Path) -> None:
    """Test exporting KB with entries to docs."""
    kb = KnowledgeBase(tmp_path)
    export_dir = tmp_path / "docs"

    # Add entries
    for i in range(3):
        entry = KnowledgeBaseEntry(
            id=f"KB-20251021-{i+1:03d}",
            title=f"Entry {i+1}",
            category="architecture",
            content=f"Content for entry {i+1}",
            tags=[f"tag{i+1}", "export"],
            created_at=datetime.now(),
            updated_at=datetime.now(),
        )
        kb.add(entry)

    # Export
    kb.export_to_markdown(export_dir)

    # Verify export
    assert export_dir.exists()
    md_files = list(export_dir.glob("*.md"))
    assert len(md_files) >= 1  # At least 1 markdown file created

    # Verify content in files
    for md_file in md_files:
        content = md_file.read_text()
        assert len(content) > 0
        # Should have markdown structure
        assert "#" in content or "Entry" in content


def test_export_to_docs_all_categories(tmp_path: Path) -> None:
    """Test exporting entries from all categories."""
    kb = KnowledgeBase(tmp_path)
    export_dir = tmp_path / "docs"

    # Add entries in all categories
    categories = ["architecture", "decision", "constraint", "pattern", "convention"]
    for i, category in enumerate(categories, 1):
        entry = KnowledgeBaseEntry(
            id=f"KB-20251021-{i:03d}",
            title=f"{category.title()} Entry",
            category=category,
            content=f"Content for {category}",
            tags=[category],
            created_at=datetime.now(),
            updated_at=datetime.now(),
        )
        kb.add(entry)

    # Export
    kb.export_to_markdown(export_dir)

    # Verify export
    assert export_dir.exists()
    md_files = list(export_dir.glob("*.md"))
    assert len(md_files) >= 1

    # Check if all categories represented
    all_content = ""
    for md_file in md_files:
        all_content += md_file.read_text()

    # At least some categories should appear
    # (exact behavior depends on implementation)
    assert len(all_content) > 100  # Substantial content


def test_export_to_docs_unicode_content(tmp_path: Path) -> None:
    """Test exporting entries with Unicode content."""
    kb = KnowledgeBase(tmp_path)
    export_dir = tmp_path / "docs"

    # Add entry with Unicode
    entry = KnowledgeBaseEntry(
        id="KB-20251021-001",
        title="日本語タイトル",
        category="architecture",
        content="これは日本語のコンテンツです。🚀",
        tags=["日本語", "unicode"],
        created_at=datetime.now(),
        updated_at=datetime.now(),
    )
    kb.add(entry)

    # Export
    kb.export_to_markdown(export_dir)

    # Verify export
    assert export_dir.exists()
    md_files = list(export_dir.glob("*.md"))
    assert len(md_files) >= 1

    # Verify Unicode preserved
    for md_file in md_files:
        content = md_file.read_text(encoding="utf-8")
        # Content should be readable
        assert len(content) > 0


def test_export_to_docs_large_dataset(tmp_path: Path) -> None:
    """Test exporting large KB dataset."""
    kb = KnowledgeBase(tmp_path)
    export_dir = tmp_path / "docs"

    # Add many entries
    categories = ["architecture", "decision", "constraint", "pattern", "convention"]
    for i in range(50):
        entry = KnowledgeBaseEntry(
            id=f"KB-20251021-{i+1:03d}",
            title=f"Entry {i+1}",
            category=categories[i % len(categories)],
            content=f"Content for entry {i+1}. " * 5,
            tags=[f"tag{i+1}", "bulk"],
            created_at=datetime.now(),
            updated_at=datetime.now(),
        )
        kb.add(entry)

    # Export
    kb.export_to_markdown(export_dir)

    # Verify export
    assert export_dir.exists()
    md_files = list(export_dir.glob("*.md"))
    assert len(md_files) >= 1

    # Verify substantial content
    total_size = sum(md_file.stat().st_size for md_file in md_files)
    assert total_size > 1000  # At least 1KB of content



    # Search with empty query
    results = kb.search("")
    # Should return empty or all entries (implementation dependent)
    assert isinstance(results, list)


def test_search_with_special_characters(tmp_path: Path) -> None:
    """Test search with special characters."""
    kb = KnowledgeBase(tmp_path)

    # Add entry
    entry = KnowledgeBaseEntry(
        id="KB-20251021-001",
        title="API <&> Design",
        category="architecture",
        content="Special chars: <>&\"'",
        tags=["special"],
        created_at=datetime.now(),
        updated_at=datetime.now(),
    )
    kb.add(entry)

    # Search with special characters
    results = kb.search("<>&")
    # Should handle gracefully
    assert isinstance(results, list)


def test_category_validation_edge_case(tmp_path: Path) -> None:
    """Test category validation with edge cases."""
    kb = KnowledgeBase(tmp_path)

    # Valid categories should work
    valid_entry = KnowledgeBaseEntry(
        id="KB-20251021-001",
        title="Valid Entry",
        category="architecture",  # Valid
        content="Valid content",
        tags=["valid"],
        created_at=datetime.now(),
        updated_at=datetime.now(),
    )
    kb.add(valid_entry)

    # Verify added
    assert kb.get("KB-20251021-001") is not None


def test_initialization_edge_cases(tmp_path: Path) -> None:
    """Test KB initialization with edge cases."""
    # Initialize with non-existent directory
    new_dir = tmp_path / "new_project"
    kb = KnowledgeBase(new_dir)

    # Should create .clauxton directory
    clauxton_dir = new_dir / ".clauxton"
    assert clauxton_dir.exists()

    # Should be able to add entries
    entry = KnowledgeBaseEntry(
        id="KB-20251021-001",
        title="First Entry",
        category="architecture",
        content="First content",
        tags=["first"],
        created_at=datetime.now(),
        updated_at=datetime.now(),
    )
    kb.add(entry)

    # Verify added
    assert kb.get("KB-20251021-001") is not None


# ============================================================================
# Path/str Compatibility Tests (v0.10.1 Bug Fix)
# ============================================================================


def test_knowledge_base_accepts_string_path(tmp_path: Path) -> None:
    """Test that KnowledgeBase accepts string paths (v0.10.1 bug fix)."""
    # Should not raise TypeError
    kb = KnowledgeBase(str(tmp_path))
    assert kb.root_dir == tmp_path
    assert kb.kb_file.exists()


def test_knowledge_base_accepts_path_object(tmp_path: Path) -> None:
    """Test that KnowledgeBase accepts Path objects."""
    kb = KnowledgeBase(tmp_path)
    assert kb.root_dir == tmp_path
    assert kb.kb_file.exists()


def test_knowledge_base_string_path_operations(tmp_path: Path) -> None:
    """Test that KnowledgeBase with string path can perform operations."""
    kb = KnowledgeBase(str(tmp_path))

    # Add entry
    entry = KnowledgeBaseEntry(
        id="KB-20251022-001",
        title="Test Entry",
        category="architecture",
        content="Test content",
        tags=["test"],
        created_at=datetime.now(),
        updated_at=datetime.now(),
    )
    entry_id = kb.add(entry)
    assert entry_id == "KB-20251022-001"

    # Get entry
    retrieved = kb.get(entry_id)
    assert retrieved is not None
    assert retrieved.title == "Test Entry"

    # List entries
    entries = kb.list_all()
    assert len(entries) == 1

    # Delete entry
    kb.delete(entry_id)

    # Verify deletion (get should raise NotFoundError)
    with pytest.raises(NotFoundError):
        kb.get(entry_id)
