"""
Tests for runtime/adapters/manager.py — AdapterManager.

Covers:
    - create_adapter resolves exact scheme matches to the correct adapter class
    - create_adapter uses sniff rules when exact scheme match fails
    - create_adapter raises ValueError for unknown/unregistered schemes
    - register rejects non-BaseAdapter subclasses (TypeError)
    - register rejects duplicate scheme registration (ValueError)
    - Custom sniff rules are applied after built-in rules
"""

from typing import Any, Dict

import pytest

from runtime.adapters.base import BaseAdapter
from runtime.adapters.manager import AdapterManager


# ── Dummy adapters for testing ───────────────────────────────────────────


class DummyMcpAdapter(BaseAdapter):
    def __init__(self, url: str) -> None:
        self.url = url

    def introspect(self) -> Dict[str, Any]:
        return {"tools": [], "version": "1.0"}

    def execute(self, tool_name: str, args: Dict[str, Any]) -> Dict[str, Any]:
        return {"status": "ok", "result": None}


class DummyRestAdapter(BaseAdapter):
    def __init__(self, url: str) -> None:
        self.url = url

    def introspect(self) -> Dict[str, Any]:
        return {"tools": [], "version": "1.0"}

    def execute(self, tool_name: str, args: Dict[str, Any]) -> Dict[str, Any]:
        return {"status": "ok", "result": None}


class DummySqlAdapter(BaseAdapter):
    def __init__(self, url: str) -> None:
        self.url = url

    def introspect(self) -> Dict[str, Any]:
        return {"tools": [], "version": "1.0"}

    def execute(self, tool_name: str, args: Dict[str, Any]) -> Dict[str, Any]:
        return {"status": "ok", "result": None}


class DummyWebSocketAdapter(BaseAdapter):
    def __init__(self, url: str) -> None:
        self.url = url

    def introspect(self) -> Dict[str, Any]:
        return {"tools": [], "version": "1.0"}

    def execute(self, tool_name: str, args: Dict[str, Any]) -> Dict[str, Any]:
        return {"status": "ok", "result": None}


class DummyOpenApiAdapter(BaseAdapter):
    def __init__(self, url: str) -> None:
        self.url = url

    def introspect(self) -> Dict[str, Any]:
        return {"tools": [], "version": "1.0"}

    def execute(self, tool_name: str, args: Dict[str, Any]) -> Dict[str, Any]:
        return {"status": "ok", "result": None}


class DummyGraphQlAdapter(BaseAdapter):
    def __init__(self, url: str) -> None:
        self.url = url

    def introspect(self) -> Dict[str, Any]:
        return {"tools": [], "version": "1.0"}

    def execute(self, tool_name: str, args: Dict[str, Any]) -> Dict[str, Any]:
        return {"status": "ok", "result": None}


# ── Fixtures ─────────────────────────────────────────────────────────────


@pytest.fixture
def manager() -> AdapterManager:
    """Return a fresh AdapterManager with dummy adapters registered."""
    mgr = AdapterManager()
    mgr.register("mcp", DummyMcpAdapter)
    mgr.register("rest", DummyRestAdapter)
    mgr.register("sql", DummySqlAdapter)
    mgr.register("websocket", DummyWebSocketAdapter)
    mgr.register("openapi", DummyOpenApiAdapter)
    mgr.register("graphql", DummyGraphQlAdapter)
    return mgr


# ── Exact scheme resolution ──────────────────────────────────────────────


class TestExactSchemeResolution:
    def test_mcp_url(self, manager: AdapterManager) -> None:
        adapter = manager.create_adapter("mcp://localhost:9000")
        assert isinstance(adapter, DummyMcpAdapter)
        assert adapter.url == "mcp://localhost:9000"

    def test_rest_url(self, manager: AdapterManager) -> None:
        adapter = manager.create_adapter("rest://api.example.com/v1")
        assert isinstance(adapter, DummyRestAdapter)

    def test_scheme_is_case_insensitive(self, manager: AdapterManager) -> None:
        adapter = manager.create_adapter("MCP://localhost:9000")
        assert isinstance(adapter, DummyMcpAdapter)

    def test_sql_direct_scheme(self, manager: AdapterManager) -> None:
        adapter = manager.create_adapter("sql://mydb")
        assert isinstance(adapter, DummySqlAdapter)


# ── Sniff rule resolution ────────────────────────────────────────────────


class TestSniffRuleResolution:
    def test_postgresql_sniffs_to_sql(self, manager: AdapterManager) -> None:
        adapter = manager.create_adapter("postgresql://user:pass@host/db")
        assert isinstance(adapter, DummySqlAdapter)

    def test_mysql_sniffs_to_sql(self, manager: AdapterManager) -> None:
        adapter = manager.create_adapter("mysql://host/db")
        assert isinstance(adapter, DummySqlAdapter)

    def test_sqlite_sniffs_to_sql(self, manager: AdapterManager) -> None:
        adapter = manager.create_adapter("sqlite:///path/to/db.sqlite")
        assert isinstance(adapter, DummySqlAdapter)

    def test_postgres_plus_driver_sniffs_to_sql(self, manager: AdapterManager) -> None:
        adapter = manager.create_adapter("postgresql+asyncpg://host/db")
        assert isinstance(adapter, DummySqlAdapter)

    def test_openapi_json_sniffs_to_openapi(self, manager: AdapterManager) -> None:
        adapter = manager.create_adapter("https://api.example.com/openapi.json")
        assert isinstance(adapter, DummyOpenApiAdapter)

    def test_swagger_json_sniffs_to_openapi(self, manager: AdapterManager) -> None:
        adapter = manager.create_adapter("https://api.example.com/swagger.json")
        assert isinstance(adapter, DummyOpenApiAdapter)

    def test_openapi_yaml_sniffs_to_openapi(self, manager: AdapterManager) -> None:
        adapter = manager.create_adapter("https://api.example.com/openapi.yaml")
        assert isinstance(adapter, DummyOpenApiAdapter)

    def test_graphql_endpoint_sniffs(self, manager: AdapterManager) -> None:
        adapter = manager.create_adapter("https://api.example.com/graphql")
        assert isinstance(adapter, DummyGraphQlAdapter)

    def test_graphql_trailing_slash(self, manager: AdapterManager) -> None:
        adapter = manager.create_adapter("https://api.example.com/graphql/")
        assert isinstance(adapter, DummyGraphQlAdapter)

    def test_ws_sniffs_to_websocket(self, manager: AdapterManager) -> None:
        adapter = manager.create_adapter("ws://localhost:8080/stream")
        assert isinstance(adapter, DummyWebSocketAdapter)

    def test_wss_sniffs_to_websocket(self, manager: AdapterManager) -> None:
        adapter = manager.create_adapter("wss://secure.example.com/stream")
        assert isinstance(adapter, DummyWebSocketAdapter)

    def test_sniffed_adapter_receives_original_url(self, manager: AdapterManager) -> None:
        url = "postgresql://user:pass@host:5432/mydb"
        adapter = manager.create_adapter(url)
        assert adapter.url == url


# ── Custom sniff rules ───────────────────────────────────────────────────


class TestCustomSniffRules:
    def test_custom_rule_resolves(self, manager: AdapterManager) -> None:
        def sniff_ftp(parsed: object) -> "str | None":
            if getattr(parsed, "scheme", "").lower() == "ftp":
                return "rest"
            return None

        manager.add_sniff_rule("ftp → rest", sniff_ftp)
        adapter = manager.create_adapter("ftp://files.example.com/data")
        assert isinstance(adapter, DummyRestAdapter)

    def test_custom_rule_returns_none_falls_through(self, manager: AdapterManager) -> None:
        def noop_rule(parsed: object) -> "str | None":
            return None

        manager.add_sniff_rule("noop", noop_rule)
        with pytest.raises(ValueError, match="No adapter registered"):
            manager.create_adapter("unknown://nowhere")


# ── Error cases ──────────────────────────────────────────────────────────


class TestErrors:
    def test_unregistered_scheme_raises(self, manager: AdapterManager) -> None:
        with pytest.raises(ValueError, match="No adapter registered"):
            manager.create_adapter("foobar://something")

    def test_error_message_includes_scheme(self, manager: AdapterManager) -> None:
        with pytest.raises(ValueError, match="foobar"):
            manager.create_adapter("foobar://something")

    def test_register_non_adapter_raises_type_error(self) -> None:
        mgr = AdapterManager()
        with pytest.raises(TypeError, match="BaseAdapter subclass"):
            mgr.register("bad", str)  # type: ignore[arg-type]

    def test_register_duplicate_scheme_raises(self) -> None:
        mgr = AdapterManager()
        mgr.register("mcp", DummyMcpAdapter)
        with pytest.raises(ValueError, match="already registered"):
            mgr.register("mcp", DummyRestAdapter)

    def test_sniff_match_but_not_registered_falls_through(self) -> None:
        """Sniff rule matches 'sql' but no sql adapter is registered → ValueError."""
        mgr = AdapterManager()
        # Only register mcp, not sql
        mgr.register("mcp", DummyMcpAdapter)
        with pytest.raises(ValueError, match="No adapter registered"):
            mgr.create_adapter("postgresql://host/db")
