# tests/conftest.py
"""Test fixtures and configuration."""
import os
import shutil
import tempfile
from typing import Generator

import pytest
import redis
from fastapi.testclient import TestClient

from app import main
from app.config import settings


@pytest.fixture(autouse=True)
def _reset_test_state() -> Generator[None, None, None]:
    """Reset test state: clear data dir and registry, but keep vectorstore index."""
    # Clean up data directory before test
    if os.path.exists(settings.DATA_DIR):
        shutil.rmtree(settings.DATA_DIR)
    
    # Clean up all per-course registries before test
    app_dir = os.path.dirname(main.__file__)
    for filename in os.listdir(app_dir):
        if filename.startswith("assets_index_") and filename.endswith(".json"):
            registry_path = os.path.join(app_dir, filename)
            try:
                os.remove(registry_path)
            except Exception:
                pass
    
    # Create data directory with initialization file for vectorstore index
    os.makedirs(settings.DATA_DIR, exist_ok=True)
    sample_file = os.path.join(settings.DATA_DIR, "_init.txt")
    with open(sample_file, "w", encoding="utf-8") as f:
        f.write("Test initialization data.")
    
    yield
    
    # Cleanup after test
    if os.path.exists(settings.DATA_DIR):
        shutil.rmtree(settings.DATA_DIR)
    app_dir = os.path.dirname(main.__file__)
    for filename in os.listdir(app_dir):
        if filename.startswith("assets_index_") and filename.endswith(".json"):
            registry_path = os.path.join(app_dir, filename)
            try:
                os.remove(registry_path)
            except Exception:
                pass


@pytest.fixture
def client() -> TestClient:
    """FastAPI test client with mocked vectorstore for testing."""
    from unittest.mock import MagicMock
    from app.services.embeddings_service import EmbeddingsService
    
    # Reset the module-level service instances before creating client
    main.embeddings_service = None
    main.vectorstore_services = {}
    main.query_services = {}
    
    # Create the test client (which runs startup to initialize services)
    test_client = TestClient(main.app)
    
    # Ensure embeddings service is initialized
    if main.embeddings_service is None:
        main.embeddings_service = EmbeddingsService(model_name=settings.MODEL_NAME)
    
    # Create a mock vectorstore service that intercepts calls
    mock_vectorstore_service = MagicMock()
    mock_vectorstore_service.create_or_append = MagicMock()
    mock_vectorstore = MagicMock()
    mock_vectorstore.add_texts = MagicMock()
    mock_vectorstore_service._vectorstore = mock_vectorstore
    
    # Replace the vectorstore service with our mock for default course
    main.vectorstore_services["default"] = mock_vectorstore_service
    
    # Create query service with mock
    from app.services.query_service import QueryService
    main.query_services["default"] = QueryService(vectorstore=mock_vectorstore)
    
    return test_client


@pytest.fixture
def client_uninitialized() -> TestClient:
    """FastAPI test client without initialized vectorstore (for testing error cases)."""
    # Reset the module-level service instances before creating client
    main.embeddings_service = None
    main.vectorstore_services = {}
    main.query_services = {}
    
    # Create the test client (which runs startup to initialize services)
    # Since Redis is not available, services will remain uninitialized
    test_client = TestClient(main.app)
    
    return test_client


@pytest.fixture
def redis_client() -> Generator[redis.Redis, None, None]:
    """Redis client for tests."""
    url = os.getenv("REDIS_URL", "redis://localhost:6379")
    r = redis.Redis.from_url(url, decode_responses=True)
    yield r
    # Cleanup: flush test data (optional)
    # r.flushdb()


@pytest.fixture
def temp_data_dir() -> Generator[str, None, None]:
    """Temporary directory for test files."""
    with tempfile.TemporaryDirectory() as tmpdir:
        yield tmpdir


@pytest.fixture
def sample_text() -> str:
    """Sample text for testing."""
    return (
        "Machine learning is a subset of artificial intelligence. "
        "It enables systems to learn and improve from experience without being programmed. "
        "Deep learning uses neural networks with multiple layers to model complex patterns."
    )


@pytest.fixture
def mock_llm_service():
    """Mocked LLM service for testing without OpenAI API calls."""
    from unittest.mock import MagicMock, patch
    from app.services.llm_service import LLMService, SynthesisResult, SourceDocument
    from langchain_core.messages import AIMessage
    
    # Create a mock ChatOpenAI that returns deterministic responses
    mock_chat = MagicMock()
    
    def mock_invoke(messages):
        """Mock invoke method that returns a realistic response."""
        return AIMessage(
            content="Based on the provided materials, machine learning is a subset of AI "
                   "that enables systems to learn without explicit programming. "
                   "Deep learning uses neural networks with multiple layers to model complex patterns."
        )
    
    mock_chat.invoke = MagicMock(side_effect=mock_invoke)
    
    # Create LLMService with mocked ChatOpenAI
    with patch('app.services.llm_service.ChatOpenAI', return_value=mock_chat):
        service = LLMService(api_key="test-key-12345")
        service._llm = mock_chat
        yield service


@pytest.fixture
def sample_rag_results():
    """Sample RAG retrieval results for testing."""
    return [
        {
            "page_content": "Machine learning is a subset of artificial intelligence that enables systems to learn and improve from experience without being explicitly programmed.",
            "metadata": {"source": "ml_intro.txt", "id": "doc_001_chunk_000"}
        },
        {
            "page_content": "Deep learning uses neural networks with multiple layers to model complex patterns in data.",
            "metadata": {"source": "deep_learning.txt", "id": "doc_002_chunk_000"}
        },
        {
            "page_content": "Neural networks are inspired by biological neurons and consist of interconnected nodes organized in layers.",
            "metadata": {"source": "neural_nets.txt", "id": "doc_003_chunk_000"}
        }
    ]
