import asyncio
from unittest.mock import AsyncMock, MagicMock, patch

import pytest

from haiku.rag.app import HaikuRAGApp
from haiku.rag.store.models.document import Document


@pytest.fixture
def app(tmp_path):
    return HaikuRAGApp(db_path=tmp_path / "test.lancedb")


@pytest.mark.asyncio
async def test_list_documents(app: HaikuRAGApp, monkeypatch):
    """Test listing documents."""
    mock_docs = [
        Document(id="1", content="doc 1"),
        Document(id="2", content="doc 2"),
    ]
    mock_client = AsyncMock()
    mock_client.list_documents.return_value = mock_docs
    # The async context manager should return the mock client itself
    mock_client.__aenter__.return_value = mock_client

    mock_rich_print = MagicMock()
    mock_console_print = MagicMock()
    monkeypatch.setattr(app, "_rich_print_document", mock_rich_print)
    monkeypatch.setattr(app.console, "print", mock_console_print)

    with patch("haiku.rag.app.HaikuRAG", return_value=mock_client):
        await app.list_documents()

    mock_client.list_documents.assert_called_once()
    assert mock_rich_print.call_count == len(mock_docs)
    mock_rich_print.assert_any_call(mock_docs[0], truncate=True)
    mock_rich_print.assert_any_call(mock_docs[1], truncate=True)


@pytest.mark.asyncio
async def test_add_document_from_text(app: HaikuRAGApp, monkeypatch):
    """Test adding a document from text."""
    mock_doc = Document(id="1", content="test document")
    mock_client = AsyncMock()
    mock_client.create_document.return_value = mock_doc
    mock_client.__aenter__.return_value = mock_client

    mock_rich_print = MagicMock()
    mock_print = MagicMock()
    monkeypatch.setattr(app, "_rich_print_document", mock_rich_print)
    monkeypatch.setattr(app.console, "print", mock_print)

    with patch("haiku.rag.app.HaikuRAG", return_value=mock_client):
        await app.add_document_from_text("test document")

    mock_client.create_document.assert_called_once()
    args, kwargs = mock_client.create_document.call_args
    assert args[0] == "test document"
    assert kwargs.get("metadata") is None
    mock_rich_print.assert_called_once_with(mock_doc, truncate=True)
    mock_print.assert_called_once_with(
        "[bold green]Document 1 added successfully.[/bold green]"
    )


@pytest.mark.asyncio
async def test_add_document_from_source(app: HaikuRAGApp, monkeypatch):
    """Test adding a document from a source path."""
    mock_doc = Document(id="1", content="test document")
    mock_client = AsyncMock()
    mock_client.create_document_from_source.return_value = mock_doc
    mock_client.__aenter__.return_value = mock_client

    mock_rich_print = MagicMock()
    mock_print = MagicMock()
    monkeypatch.setattr(app, "_rich_print_document", mock_rich_print)
    monkeypatch.setattr(app.console, "print", mock_print)

    file_path = "test.txt"
    with patch("haiku.rag.app.HaikuRAG", return_value=mock_client):
        await app.add_document_from_source(file_path)

    mock_client.create_document_from_source.assert_called_once()
    args, kwargs = mock_client.create_document_from_source.call_args
    assert args[0] == file_path
    assert kwargs.get("title") is None
    assert kwargs.get("metadata") is None
    mock_rich_print.assert_called_once_with(mock_doc, truncate=True)
    mock_print.assert_called_once_with(
        "[bold green]Document 1 added successfully.[/bold green]"
    )


@pytest.mark.asyncio
async def test_get_document(app: HaikuRAGApp, monkeypatch):
    """Test getting a document."""
    mock_doc = Document(id="1", content="test document")
    mock_client = AsyncMock()
    mock_client.get_document_by_id.return_value = mock_doc
    mock_client.__aenter__.return_value = mock_client

    mock_rich_print = MagicMock()
    monkeypatch.setattr(app, "_rich_print_document", mock_rich_print)

    with patch("haiku.rag.app.HaikuRAG", return_value=mock_client):
        await app.get_document("1")

    mock_client.get_document_by_id.assert_called_once_with("1")
    mock_rich_print.assert_called_once_with(mock_doc, truncate=False)


@pytest.mark.asyncio
async def test_get_document_not_found(app: HaikuRAGApp, monkeypatch):
    """Test getting a document that does not exist."""
    mock_client = AsyncMock()
    mock_client.get_document_by_id.return_value = None
    mock_client.__aenter__.return_value = mock_client

    mock_print = MagicMock()
    monkeypatch.setattr(app.console, "print", mock_print)

    with patch("haiku.rag.app.HaikuRAG", return_value=mock_client):
        await app.get_document("1")

    mock_client.get_document_by_id.assert_called_once_with("1")
    mock_print.assert_called_once_with("[red]Document with id 1 not found.[/red]")


@pytest.mark.asyncio
async def test_delete_document(app: HaikuRAGApp, monkeypatch):
    """Test deleting a document."""
    mock_client = AsyncMock()
    mock_client.__aenter__.return_value = mock_client

    mock_print = MagicMock()
    monkeypatch.setattr(app.console, "print", mock_print)

    with patch("haiku.rag.app.HaikuRAG", return_value=mock_client):
        await app.delete_document("1")

    mock_client.delete_document.assert_called_once_with("1")
    mock_print.assert_called_once_with(
        "[bold green]Document 1 deleted successfully.[/bold green]"
    )


@pytest.mark.asyncio
async def test_search(app: HaikuRAGApp, monkeypatch):
    """Test searching for documents."""
    mock_results = [("chunk1", 0.9), ("chunk2", 0.8)]
    mock_client = AsyncMock()
    mock_client.search.return_value = mock_results
    mock_client.__aenter__.return_value = mock_client

    mock_rich_print_search = MagicMock()
    monkeypatch.setattr(app, "_rich_print_search_result", mock_rich_print_search)

    with patch("haiku.rag.app.HaikuRAG", return_value=mock_client):
        await app.search("query")

    mock_client.search.assert_called_once_with("query", limit=5)
    assert mock_rich_print_search.call_count == len(mock_results)


@pytest.mark.asyncio
async def test_search_no_results(app: HaikuRAGApp, monkeypatch):
    """Test searching with no results."""
    mock_client = AsyncMock()
    mock_client.search.return_value = []
    mock_client.__aenter__.return_value = mock_client

    mock_print = MagicMock()
    monkeypatch.setattr(app.console, "print", mock_print)

    with patch("haiku.rag.app.HaikuRAG", return_value=mock_client):
        await app.search("query")

    mock_client.search.assert_called_once_with("query", limit=5)
    mock_print.assert_called_once_with("[yellow]No results found.[/yellow]")


@pytest.mark.asyncio
@pytest.mark.parametrize("transport", ["stdio", "http", None])
async def test_serve(app: HaikuRAGApp, monkeypatch, transport):
    """Test the serve method with different transports."""
    mock_server = AsyncMock()
    mock_watcher = MagicMock()
    mock_task = asyncio.create_task(asyncio.sleep(0))
    mock_task.cancel = MagicMock()

    monkeypatch.setattr(
        "haiku.rag.app.create_mcp_server", MagicMock(return_value=mock_server)
    )
    monkeypatch.setattr(
        "haiku.rag.app.FileWatcher", MagicMock(return_value=mock_watcher)
    )
    monkeypatch.setattr("asyncio.create_task", MagicMock(return_value=mock_task))

    mock_client = AsyncMock()
    mock_client.__aenter__.return_value = mock_client

    with patch("haiku.rag.app.HaikuRAG", return_value=mock_client):
        if transport:
            await app.serve(transport=transport)
        else:
            await app.serve()

    if transport == "stdio":
        mock_server.run_stdio_async.assert_called_once()
    else:
        mock_server.run_http_async.assert_called_once_with(transport="streamable-http")

    mock_task.cancel.assert_called_once()


@pytest.mark.asyncio
async def test_ask_without_cite(app: HaikuRAGApp, monkeypatch):
    """Test asking a question without citations."""
    mock_answer = "Test answer"
    mock_client = AsyncMock()
    mock_client.ask.return_value = mock_answer
    mock_client.__aenter__.return_value = mock_client

    mock_print = MagicMock()
    monkeypatch.setattr(app.console, "print", mock_print)

    with patch("haiku.rag.app.HaikuRAG", return_value=mock_client):
        await app.ask("test question")

    mock_client.ask.assert_called_once_with("test question", cite=False)


@pytest.mark.asyncio
async def test_ask_with_cite(app: HaikuRAGApp, monkeypatch):
    """Test asking a question with citations."""
    mock_answer = "Test answer with citations"
    mock_client = AsyncMock()
    mock_client.ask.return_value = mock_answer
    mock_client.__aenter__.return_value = mock_client

    mock_print = MagicMock()
    monkeypatch.setattr(app.console, "print", mock_print)

    with patch("haiku.rag.app.HaikuRAG", return_value=mock_client):
        await app.ask("test question", cite=True)

    mock_client.ask.assert_called_once_with("test question", cite=True)
