"""Tests for qremotex.execution.tool_executor — RemoteToolExecutor."""

import asyncio
import os
from unittest import mock
from unittest.mock import AsyncMock

import pytest

from qremotex.config import ExecutorConfig
from qremotex.execution.tool_executor import RemoteToolExecutor


def _make_config(**overrides: str) -> ExecutorConfig:
    """Build a config with sane test defaults."""
    env = {
        "QRAPTOR_GATEWAY_URL": "wss://gw.qraptor.io/ws/executor",
        "QRAPTOR_API_KEY": "qrx_test_key123",
        "EXEC_TIMEOUT_SEC": "10",
    }
    env.update(overrides)
    with mock.patch.dict(os.environ, env, clear=False):
        return ExecutorConfig()


class TestToolAllowed:
    """Allowlist / blocklist checks (C7)."""

    def test_all_allowed_by_default(self) -> None:
        cfg = _make_config()
        ex = RemoteToolExecutor(cfg)
        assert ex._check_tool_allowed("system.db_query") is None
        assert ex._check_tool_allowed("project.crm_api") is None

    def test_blocklist_rejects(self) -> None:
        cfg = _make_config(BLOCKED_TOOL_KEYS="system.db_query,project.dangerous")
        ex = RemoteToolExecutor(cfg)
        assert ex._check_tool_allowed("system.db_query") is not None
        assert ex._check_tool_allowed("project.dangerous") is not None
        assert ex._check_tool_allowed("system.http_request") is None  # not blocked

    def test_allowlist_rejects_unlisted(self) -> None:
        cfg = _make_config(ALLOWED_TOOL_KEYS="system.db_query,system.http_request")
        ex = RemoteToolExecutor(cfg)
        assert ex._check_tool_allowed("system.db_query") is None
        assert ex._check_tool_allowed("system.http_request") is None
        assert ex._check_tool_allowed("system.web_crawler") is not None

    def test_system_tools_disabled(self) -> None:
        cfg = _make_config(ENABLE_SYSTEM_TOOLS="false")
        ex = RemoteToolExecutor(cfg)
        err = ex._check_tool_allowed("system.db_query")
        assert err is not None and "disabled" in err
        assert ex._check_tool_allowed("project.crm_api") is None

    def test_user_tools_disabled(self) -> None:
        cfg = _make_config(ENABLE_USER_TOOLS="false")
        ex = RemoteToolExecutor(cfg)
        assert ex._check_tool_allowed("system.db_query") is None
        err = ex._check_tool_allowed("project.crm_api")
        assert err is not None and "disabled" in err


class TestNativeExecution:
    """NATIVE tool execution tests."""

    @pytest.mark.asyncio
    async def test_native_calls_handler(self) -> None:
        cfg = _make_config()
        ex = RemoteToolExecutor(cfg)

        # Replace registry with a mock handler
        mock_handler = AsyncMock(return_value={"rows": [{"id": 1}], "row_count": 1})
        ex._registry._handlers["system.db_query"] = mock_handler

        result = await ex.execute(
            tool_key="system.db_query",
            tool_params={"query": "SELECT 1"},
            tool_definition={"execution_target_kind": "NATIVE", "timeout_seconds": 5},
            context={"run_id": "r1", "request_id": "req1"},
        )

        assert result["success"] is True
        assert result["result"]["row_count"] == 1
        assert result["duration_ms"] >= 0
        mock_handler.assert_awaited_once()

    @pytest.mark.asyncio
    async def test_native_missing_handler(self) -> None:
        cfg = _make_config()
        ex = RemoteToolExecutor(cfg)

        result = await ex.execute(
            tool_key="system.nonexistent",
            tool_params={},
            tool_definition={"execution_target_kind": "NATIVE", "timeout_seconds": 5},
            context={},
        )

        assert result["success"] is False
        assert "No native handler" in result["error"]

    @pytest.mark.asyncio
    async def test_native_timeout(self) -> None:
        cfg = _make_config()
        ex = RemoteToolExecutor(cfg)

        async def slow_handler(params: dict, ctx: dict) -> dict:
            await asyncio.sleep(10)
            return {}

        ex._registry._handlers["system.slow"] = slow_handler

        result = await ex.execute(
            tool_key="system.slow",
            tool_params={},
            tool_definition={"execution_target_kind": "NATIVE", "timeout_seconds": 0.1},
            context={},
        )

        assert result["success"] is False
        assert "timed out" in result["error"]

    @pytest.mark.asyncio
    async def test_native_handler_exception(self) -> None:
        cfg = _make_config()
        ex = RemoteToolExecutor(cfg)

        async def broken_handler(params: dict, ctx: dict) -> dict:
            raise RuntimeError("connection failed")

        ex._registry._handlers["system.broken"] = broken_handler

        result = await ex.execute(
            tool_key="system.broken",
            tool_params={},
            tool_definition={"execution_target_kind": "NATIVE", "timeout_seconds": 5},
            context={},
        )

        assert result["success"] is False
        assert "RuntimeError" in result["error"]
        assert "connection failed" in result["error"]


class TestBlockedToolExecution:
    """Verify allowlist/blocklist enforcement in execute()."""

    @pytest.mark.asyncio
    async def test_blocked_tool_returns_error(self) -> None:
        cfg = _make_config(BLOCKED_TOOL_KEYS="system.db_query")
        ex = RemoteToolExecutor(cfg)

        result = await ex.execute(
            tool_key="system.db_query",
            tool_params={},
            tool_definition={"execution_target_kind": "NATIVE", "timeout_seconds": 5},
            context={},
        )

        assert result["success"] is False
        assert "blocked" in result["error"]


class TestUnsupportedKind:
    """Unknown execution_target_kind."""

    @pytest.mark.asyncio
    async def test_unsupported_kind(self) -> None:
        cfg = _make_config()
        ex = RemoteToolExecutor(cfg)

        result = await ex.execute(
            tool_key="system.db_query",
            tool_params={},
            tool_definition={"execution_target_kind": "UNKNOWN_KIND"},
            context={},
        )

        assert result["success"] is False
        assert "Unsupported" in result["error"]

    @pytest.mark.asyncio
    async def test_defaults_to_native_kind(self) -> None:
        cfg = _make_config()
        ex = RemoteToolExecutor(cfg)

        # No execution_target_kind → defaults to NATIVE
        mock_handler = AsyncMock(return_value={"ok": True})
        ex._registry._handlers["system.db_query"] = mock_handler

        result = await ex.execute(
            tool_key="system.db_query",
            tool_params={},
            tool_definition={},
            context={},
        )

        assert result["success"] is True
        mock_handler.assert_awaited_once()
