"""Tests for context gatherers."""

from unittest.mock import AsyncMock, MagicMock, patch

import pytest

from mirdan.config import MCPClientConfig, MirdanConfig, OrchestrationConfig
from mirdan.core.client_registry import MCPClientRegistry
from mirdan.core.gatherers import (
    Context7Gatherer,
    EnyalGatherer,
    FilesystemGatherer,
    GitHubGatherer,
)
from mirdan.models import Intent, TaskType


@pytest.fixture
def empty_config() -> MirdanConfig:
    """Create a config with no MCP clients."""
    return MirdanConfig()


@pytest.fixture
def full_config() -> MirdanConfig:
    """Create a config with all MCP clients."""
    config = MirdanConfig()
    config.orchestration = OrchestrationConfig(
        mcp_clients={
            "context7": MCPClientConfig(type="http", url="https://example.com"),
            "filesystem": MCPClientConfig(type="stdio", command="npx", args=["test"]),
            "enyal": MCPClientConfig(type="stdio", command="uvx", args=["enyal"]),
            "github": MCPClientConfig(type="stdio", command="npx", args=["github"]),
        }
    )
    return config


@pytest.fixture
def generation_intent() -> Intent:
    """Create a generation intent."""
    return Intent(
        original_prompt="create a FastAPI endpoint",
        task_type=TaskType.GENERATION,
        primary_language="python",
        frameworks=["fastapi"],
    )


@pytest.fixture
def debug_intent() -> Intent:
    """Create a debug intent."""
    return Intent(
        original_prompt="fix the login bug",
        task_type=TaskType.DEBUG,
        primary_language="python",
    )


@pytest.fixture
def no_framework_intent() -> Intent:
    """Create an intent with no frameworks."""
    return Intent(
        original_prompt="do something",
        task_type=TaskType.UNKNOWN,
    )


class TestContext7Gatherer:
    """Tests for Context7Gatherer."""

    @pytest.mark.asyncio
    async def test_is_available_returns_false_when_not_configured(
        self, empty_config: MirdanConfig
    ) -> None:
        """Should return False when context7 is not configured."""
        registry = MCPClientRegistry(empty_config)
        gatherer = Context7Gatherer(registry)
        assert await gatherer.is_available() is False

    @pytest.mark.asyncio
    async def test_is_available_returns_true_when_configured(
        self, full_config: MirdanConfig
    ) -> None:
        """Should return True when context7 is configured."""
        registry = MCPClientRegistry(full_config)
        gatherer = Context7Gatherer(registry)
        assert await gatherer.is_available() is True

    @pytest.mark.asyncio
    async def test_gather_with_no_frameworks_returns_empty(
        self, full_config: MirdanConfig, no_framework_intent: Intent
    ) -> None:
        """Should return empty context when no frameworks detected."""
        registry = MCPClientRegistry(full_config)
        gatherer = Context7Gatherer(registry)

        result = await gatherer.gather(no_framework_intent)

        assert result.success is True
        assert len(result.context.documentation_hints) == 0
        assert result.metadata.get("reason") == "no_frameworks"

    @pytest.mark.asyncio
    async def test_gather_fetches_docs_for_frameworks(
        self, full_config: MirdanConfig, generation_intent: Intent
    ) -> None:
        """Should fetch docs for each framework in intent."""
        registry = MCPClientRegistry(full_config)
        gatherer = Context7Gatherer(registry)

        # Mock the client
        mock_client = AsyncMock()
        mock_result = MagicMock()
        mock_content = MagicMock()
        mock_content.text = "Context7-compatible library ID: /pallets/flask\nSome docs here"
        mock_result.content = [mock_content]
        mock_client.call_tool = AsyncMock(return_value=mock_result)
        mock_client.__aenter__ = AsyncMock(return_value=mock_client)
        mock_client.__aexit__ = AsyncMock(return_value=None)

        with patch.object(registry, "get_client", return_value=mock_client):
            result = await gatherer.gather(generation_intent)

        assert result.success is True
        assert mock_client.call_tool.called

    @pytest.mark.asyncio
    async def test_gather_handles_unavailable_mcp(
        self, empty_config: MirdanConfig, generation_intent: Intent
    ) -> None:
        """Should handle when MCP is not available."""
        registry = MCPClientRegistry(empty_config)
        gatherer = Context7Gatherer(registry)

        result = await gatherer.gather(generation_intent)

        assert result.success is False
        assert result.error is not None
        assert "not available" in result.error

    def test_name_property(self, empty_config: MirdanConfig) -> None:
        """Should return correct name."""
        registry = MCPClientRegistry(empty_config)
        gatherer = Context7Gatherer(registry)
        assert gatherer.name == "Context7Gatherer"

    def test_required_mcp_property(self, empty_config: MirdanConfig) -> None:
        """Should return correct required MCP."""
        registry = MCPClientRegistry(empty_config)
        gatherer = Context7Gatherer(registry)
        assert gatherer.required_mcp == "context7"


class TestFilesystemGatherer:
    """Tests for FilesystemGatherer."""

    @pytest.mark.asyncio
    async def test_is_available_returns_false_when_not_configured(
        self, empty_config: MirdanConfig
    ) -> None:
        """Should return False when filesystem is not configured."""
        registry = MCPClientRegistry(empty_config)
        gatherer = FilesystemGatherer(registry)
        assert await gatherer.is_available() is False

    @pytest.mark.asyncio
    async def test_is_available_returns_true_when_configured(
        self, full_config: MirdanConfig
    ) -> None:
        """Should return True when filesystem is configured."""
        registry = MCPClientRegistry(full_config)
        gatherer = FilesystemGatherer(registry)
        assert await gatherer.is_available() is True

    @pytest.mark.asyncio
    async def test_gather_searches_by_language(
        self, full_config: MirdanConfig, generation_intent: Intent
    ) -> None:
        """Should search for files matching language patterns."""
        registry = MCPClientRegistry(full_config)
        gatherer = FilesystemGatherer(registry)

        mock_client = AsyncMock()
        mock_result = MagicMock()
        mock_content = MagicMock()
        mock_content.text = "src/main.py\nsrc/utils.py"
        mock_result.content = [mock_content]
        mock_client.call_tool = AsyncMock(return_value=mock_result)
        mock_client.__aenter__ = AsyncMock(return_value=mock_client)
        mock_client.__aexit__ = AsyncMock(return_value=None)

        with patch.object(registry, "get_client", return_value=mock_client):
            result = await gatherer.gather(generation_intent)

        assert result.success is True
        assert result.metadata.get("language") == "python"

    def test_name_property(self, empty_config: MirdanConfig) -> None:
        """Should return correct name."""
        registry = MCPClientRegistry(empty_config)
        gatherer = FilesystemGatherer(registry)
        assert gatherer.name == "FilesystemGatherer"

    def test_required_mcp_property(self, empty_config: MirdanConfig) -> None:
        """Should return correct required MCP."""
        registry = MCPClientRegistry(empty_config)
        gatherer = FilesystemGatherer(registry)
        assert gatherer.required_mcp == "filesystem"


class TestEnyalGatherer:
    """Tests for EnyalGatherer."""

    @pytest.mark.asyncio
    async def test_is_available_returns_false_when_not_configured(
        self, empty_config: MirdanConfig
    ) -> None:
        """Should return False when enyal is not configured."""
        registry = MCPClientRegistry(empty_config)
        gatherer = EnyalGatherer(registry)
        assert await gatherer.is_available() is False

    @pytest.mark.asyncio
    async def test_is_available_returns_true_when_configured(
        self, full_config: MirdanConfig
    ) -> None:
        """Should return True when enyal is configured."""
        registry = MCPClientRegistry(full_config)
        gatherer = EnyalGatherer(registry)
        assert await gatherer.is_available() is True

    @pytest.mark.asyncio
    async def test_gather_builds_query_from_intent(
        self, full_config: MirdanConfig, generation_intent: Intent
    ) -> None:
        """Should build appropriate query from intent."""
        registry = MCPClientRegistry(full_config)
        gatherer = EnyalGatherer(registry)

        mock_client = AsyncMock()
        mock_result = MagicMock()
        mock_content = MagicMock()
        mock_content.text = '{"results": []}'
        mock_result.content = [mock_content]
        mock_client.call_tool = AsyncMock(return_value=mock_result)
        mock_client.__aenter__ = AsyncMock(return_value=mock_client)
        mock_client.__aexit__ = AsyncMock(return_value=None)

        with patch.object(registry, "get_client", return_value=mock_client):
            result = await gatherer.gather(generation_intent)

        assert result.success is True
        # Check query was built with language
        assert "python" in result.metadata.get("query", "")

    def test_build_query_includes_language(
        self, empty_config: MirdanConfig, generation_intent: Intent
    ) -> None:
        """Should include language in query."""
        registry = MCPClientRegistry(empty_config)
        gatherer = EnyalGatherer(registry)

        query = gatherer._build_query(generation_intent)

        assert "python" in query

    def test_build_query_includes_frameworks(
        self, empty_config: MirdanConfig, generation_intent: Intent
    ) -> None:
        """Should include frameworks in query."""
        registry = MCPClientRegistry(empty_config)
        gatherer = EnyalGatherer(registry)

        query = gatherer._build_query(generation_intent)

        assert "fastapi" in query

    def test_name_property(self, empty_config: MirdanConfig) -> None:
        """Should return correct name."""
        registry = MCPClientRegistry(empty_config)
        gatherer = EnyalGatherer(registry)
        assert gatherer.name == "EnyalGatherer"

    def test_required_mcp_property(self, empty_config: MirdanConfig) -> None:
        """Should return correct required MCP."""
        registry = MCPClientRegistry(empty_config)
        gatherer = EnyalGatherer(registry)
        assert gatherer.required_mcp == "enyal"


class TestGitHubGatherer:
    """Tests for GitHubGatherer."""

    @pytest.mark.asyncio
    async def test_is_available_returns_false_when_not_configured(
        self, empty_config: MirdanConfig
    ) -> None:
        """Should return False when github is not configured."""
        registry = MCPClientRegistry(empty_config)
        gatherer = GitHubGatherer(registry)
        assert await gatherer.is_available() is False

    @pytest.mark.asyncio
    async def test_is_available_returns_true_when_configured(
        self, full_config: MirdanConfig
    ) -> None:
        """Should return True when github is configured."""
        registry = MCPClientRegistry(full_config)
        gatherer = GitHubGatherer(registry)
        assert await gatherer.is_available() is True

    @pytest.mark.asyncio
    async def test_gather_without_repo_context_returns_empty(
        self, full_config: MirdanConfig, generation_intent: Intent
    ) -> None:
        """Should return empty when no repo context is set."""
        registry = MCPClientRegistry(full_config)
        gatherer = GitHubGatherer(registry)

        result = await gatherer.gather(generation_intent)

        assert result.success is True
        assert result.metadata.get("reason") == "no_repo_context"

    @pytest.mark.asyncio
    async def test_gather_with_repo_context(
        self, full_config: MirdanConfig, generation_intent: Intent
    ) -> None:
        """Should gather context when repo is set."""
        registry = MCPClientRegistry(full_config)
        gatherer = GitHubGatherer(registry)
        gatherer.set_repo_context("owner", "repo")

        mock_client = AsyncMock()
        mock_result = MagicMock()
        mock_content = MagicMock()
        mock_content.text = '{"dependencies": {"fastapi": "^0.100.0"}}'
        mock_result.content = [mock_content]
        mock_client.call_tool = AsyncMock(return_value=mock_result)
        mock_client.__aenter__ = AsyncMock(return_value=mock_client)
        mock_client.__aexit__ = AsyncMock(return_value=None)

        with patch.object(registry, "get_client", return_value=mock_client):
            result = await gatherer.gather(generation_intent)

        assert result.success is True
        assert result.metadata.get("owner") == "owner"
        assert result.metadata.get("repo") == "repo"

    def test_set_repo_context(self, empty_config: MirdanConfig) -> None:
        """Should set repo context correctly."""
        registry = MCPClientRegistry(empty_config)
        gatherer = GitHubGatherer(registry)

        gatherer.set_repo_context("myorg", "myrepo")

        assert gatherer._owner == "myorg"
        assert gatherer._repo == "myrepo"

    def test_name_property(self, empty_config: MirdanConfig) -> None:
        """Should return correct name."""
        registry = MCPClientRegistry(empty_config)
        gatherer = GitHubGatherer(registry)
        assert gatherer.name == "GitHubGatherer"

    def test_required_mcp_property(self, empty_config: MirdanConfig) -> None:
        """Should return correct required MCP."""
        registry = MCPClientRegistry(empty_config)
        gatherer = GitHubGatherer(registry)
        assert gatherer.required_mcp == "github"
