import pytest
from unittest.mock import MagicMock, AsyncMock
from fastapi import FastAPI
from fastapi.testclient import TestClient
from sqlalchemy.orm import Session
from datetime import datetime

# Import the dependencies and router factory
from app.core.services import RAGService
from app.api.dependencies import get_db
from app.api.routes import create_api_router

@pytest.fixture
def client():
    """
    Pytest fixture to create a TestClient with a fully mocked environment.
    This creates an isolated FastAPI app for each test.
    """
    test_app = FastAPI()
    mock_rag_service = MagicMock(spec=RAGService)
    mock_db_session = MagicMock(spec=Session)

    def override_get_db():
        yield mock_db_session

    api_router = create_api_router(rag_service=mock_rag_service)
    test_app.dependency_overrides[get_db] = override_get_db
    test_app.include_router(api_router)

    yield TestClient(test_app), mock_rag_service

# --- Root Endpoint ---

def test_read_root(client):
    """Tests the root endpoint to ensure the API is running."""
    test_client, _ = client
    response = test_client.get("/")
    assert response.status_code == 200
    assert response.json() == {"status": "AI Model Hub is running!"}

# --- Chat Endpoints ---

def test_chat_handler_success(client):
    """Tests a successful chat request."""
    test_client, mock_rag_service = client
    mock_rag_service.chat_with_rag.return_value = "This is a mocked RAG response."
    
    response = test_client.post("/chat", json={"prompt": "Hello!", "model": "gemini"})
    
    assert response.status_code == 200
    assert response.json()["answer"] == "This is a mocked RAG response."
    mock_rag_service.chat_with_rag.assert_called_once()

def test_chat_handler_validation_error(client):
    """Tests the chat endpoint with invalid data (an empty prompt)."""
    test_client, _ = client
    response = test_client.post("/chat", json={"prompt": "", "model": "deepseek"})
    assert response.status_code == 422

def test_chat_handler_internal_error(client):
    """Tests the chat endpoint when the RAG service raises an exception."""
    test_client, mock_rag_service = client
    error_message = "LLM provider is down"
    mock_rag_service.chat_with_rag.side_effect = Exception(error_message)
    
    response = test_client.post("/chat", json={"prompt": "A valid question", "model": "deepseek"})
    
    assert response.status_code == 500
    assert error_message in response.json()["detail"]

# --- Document Endpoints ---

def test_add_document_success(client):
    """Tests successfully adding a document."""
    test_client, mock_rag_service = client
    mock_rag_service.add_document.return_value = 123
    doc_payload = {"title": "Test Doc", "text": "Content here"}

    # FIX: Use the correct plural URL '/documents'
    response = test_client.post("/documents", json=doc_payload)

    assert response.status_code == 200
    assert response.json()["message"] == "Document 'Test Doc' added successfully with ID 123"
    mock_rag_service.add_document.assert_called_once()

def test_get_documents_success(client):
    """Tests successfully retrieving a list of all documents."""
    test_client, mock_rag_service = client
    # Arrange: Create mock document data that the service will return
    mock_docs = [
        {"id": 1, "title": "Doc One", "status": "ready", "created_at": datetime.now()},
        {"id": 2, "title": "Doc Two", "status": "processing", "created_at": datetime.now()}
    ]
    mock_rag_service.get_all_documents.return_value = mock_docs

    # Act
    response = test_client.get("/documents")
    
    # Assert
    assert response.status_code == 200
    response_data = response.json()
    assert len(response_data["documents"]) == 2
    assert response_data["documents"][0]["title"] == "Doc One"
    mock_rag_service.get_all_documents.assert_called_once()

def test_delete_document_success(client):
    """Tests successfully deleting a document."""
    test_client, mock_rag_service = client
    # Arrange: Mock the service to confirm deletion of document ID 42
    mock_rag_service.delete_document.return_value = 42

    # Act
    response = test_client.delete("/documents/42")
    
    # Assert
    assert response.status_code == 200
    assert response.json()["message"] == "Document deleted successfully"
    assert response.json()["document_id"] == 42
    mock_rag_service.delete_document.assert_called_once_with(db=mock_rag_service.delete_document.call_args.kwargs['db'], document_id=42)

def test_delete_document_not_found(client):
    """Tests attempting to delete a document that does not exist."""
    test_client, mock_rag_service = client
    # Arrange: Mock the service to indicate the document was not found
    mock_rag_service.delete_document.return_value = None

    # Act
    response = test_client.delete("/documents/999")
    
    # Assert
    assert response.status_code == 404
    assert response.json()["detail"] == "Document with ID 999 not found."