from __future__ import annotations

from typing import TYPE_CHECKING

import pytest
from ktem.mcp.db import MCPTable
from sqlalchemy import create_engine
from sqlalchemy.orm import Session

if TYPE_CHECKING:
    from ktem.mcp.manager import MCPManager
    from pytest_mock.plugin import MockerFixture


# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------


@pytest.fixture
def test_engine():
    """Create an in-memory SQLite engine with MCPTable schema."""
    engine = create_engine("sqlite:///:memory:")
    MCPTable.metadata.create_all(engine)
    return engine


@pytest.fixture
def manager(test_engine, mocker: MockerFixture) -> MCPManager:
    """Fresh MCPManager with a mocked in-memory DB for each test."""
    mocker.patch("ktem.mcp.manager.engine", test_engine)
    from ktem.mcp.manager import MCPManager

    return MCPManager()


# ---------------------------------------------------------------------------
# MCPManager.add tests
# ---------------------------------------------------------------------------


def test_add_and_retrieve(manager: MCPManager) -> None:
    """add() persists data; get() and info() reflect it."""
    manager.add("server1", {"command": "uvx", "args": ["mcp-server-fetch"]})
    assert manager.info()["server1"]["config"]["command"] == "uvx"
    entry = manager.get("server1")
    assert entry is not None
    assert entry["name"] == "server1"


def test_add_multiple(manager: MCPManager) -> None:
    """Multiple servers can be added."""
    manager.add("s1", {"command": "cmd1"})
    manager.add("s2", {"command": "cmd2"})
    assert set(manager.info().keys()) == {"s1", "s2"}


@pytest.mark.parametrize("name", ["", "   "])
def test_add_empty_or_whitespace_name_raises(
    manager: MCPManager,
    name: str,
) -> None:
    """Empty or whitespace-only name raises ValueError."""
    with pytest.raises(ValueError, match="Name must not be empty"):
        manager.add(name, {})


def test_add_whitespace_name_is_stripped(manager: MCPManager) -> None:
    """Whitespace in name is stripped."""
    manager.add("  server1  ", {"command": "uvx"})
    assert "server1" in manager.info()


def test_add_complex_config_stored_correctly(manager: MCPManager) -> None:
    """Complex config with nested data is stored correctly."""
    config = {
        "command": "uvx",
        "env": {"JIRA_URL": "https://example.atlassian.net"},
        "enabled_tools": ["jira_search"],
    }
    manager.add("atlassian", config)
    entry = manager.get("atlassian")
    assert entry is not None
    stored = entry["config"]
    assert stored["env"]["JIRA_URL"] == "https://example.atlassian.net"
    assert stored["enabled_tools"] == ["jira_search"]


# ---------------------------------------------------------------------------
# MCPManager.update and delete tests
# ---------------------------------------------------------------------------


def test_update_changes_config(manager: MCPManager) -> None:
    """update() changes config for specified server only."""
    manager.add("s1", {"command": "cmd1"})
    manager.add("s2", {"command": "cmd2"})
    manager.update("s1", {"command": "updated"})
    assert manager.info()["s1"]["config"]["command"] == "updated"
    assert manager.info()["s2"]["config"]["command"] == "cmd2"  # untouched


def test_update_nonexistent_raises(manager: MCPManager) -> None:
    """Updating nonexistent server raises ValueError."""
    with pytest.raises(ValueError, match="not found"):
        manager.update("ghost", {})


def test_delete_removes_entry(manager: MCPManager) -> None:
    """delete() removes the specified entry."""
    manager.add("s1", {})
    manager.add("s2", {})
    manager.delete("s1")
    assert "s1" not in manager.info()
    assert "s2" in manager.info()


def test_delete_nonexistent_is_noop(manager: MCPManager) -> None:
    """Deleting nonexistent server is a no-op."""
    manager.delete("ghost")  # must not raise
    assert len(manager.info()) == 0


# ---------------------------------------------------------------------------
# MCPManager.list_registered_mcp_servers tests
# ---------------------------------------------------------------------------


def test_list_registered_mcp_servers(manager: MCPManager) -> None:
    """All configured servers are listed with the MCP prefix."""
    manager.add("no_filter", {"command": "uvx"})
    manager.add("with_filter", {"command": "uvx", "enabled_tools": ["tool_a"]})
    choices = manager.list_registered_mcp_servers()
    assert "[MCP] no_filter" in choices
    assert "[MCP] with_filter" in choices


def test_list_registered_mcp_servers_empty_when_no_servers(manager: MCPManager) -> None:
    """Returns empty list when no servers are configured."""
    assert manager.list_registered_mcp_servers() == []


# ---------------------------------------------------------------------------
# MCPManager.load tests
# ---------------------------------------------------------------------------


def test_load_picks_up_external_db_changes(
    manager: MCPManager,
    test_engine,
) -> None:
    """load() refreshes from database, picking up external changes."""
    manager.add("server1", {})
    with Session(test_engine) as session:
        session.add(MCPTable(name="external", config={"command": "ext"}))
        session.commit()

    assert "external" not in manager.info()  # not yet refreshed
    manager.load()
    assert "external" in manager.info()
