"""Tests for Discord action confirmation system using native Agno RunOutput.

Covers tool safety classification, formatting, impact levels,
and the confirmation flow in _handle_message.
"""

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

import pytest

# ---------------------------------------------------------------------------
# _format_action_description (imported from tool_safety)
# ---------------------------------------------------------------------------


class TestFormatActionDescription:
    def test_shell_command_string(self):
        from integrations.chat_bridge.vendors.discord import _format_action_description

        desc = _format_action_description("shell", {"command": "ls -la /tmp"})
        assert "ls -la /tmp" in desc

    def test_shell_command_list(self):
        from integrations.chat_bridge.vendors.discord import _format_action_description

        desc = _format_action_description("shell", {"args": ["mkdir", "-p", "/tmp/test"]})
        assert "mkdir -p /tmp/test" in desc

    def test_write_file(self):
        from integrations.chat_bridge.vendors.discord import _format_action_description

        desc = _format_action_description(
            "write_file", {"file_name": "/tmp/foo.md", "contents": "hello world"}
        )
        assert "/tmp/foo.md" in desc
        assert "11 chars" in desc

    def test_python_code(self):
        from integrations.chat_bridge.vendors.discord import _format_action_description

        desc = _format_action_description("python", {"code": "print(42)"})
        assert "print(42)" in desc

    def test_unknown_tool(self):
        from integrations.chat_bridge.vendors.discord import _format_action_description

        desc = _format_action_description("custom_tool", {"key": "value"})
        assert "custom_tool" in desc


# ---------------------------------------------------------------------------
# _get_impact_level (imported from tool_safety)
# ---------------------------------------------------------------------------


class TestGetImpactLevel:
    def test_high_impact(self):
        from integrations.chat_bridge.vendors.discord import _get_impact_level

        assert _get_impact_level("shell") == "high"
        assert _get_impact_level("python") == "high"

    def test_medium_impact(self):
        from integrations.chat_bridge.vendors.discord import _get_impact_level

        assert _get_impact_level("write_file") == "medium"
        assert _get_impact_level("aider") == "medium"

    def test_low_impact(self):
        from integrations.chat_bridge.vendors.discord import _get_impact_level

        assert _get_impact_level("web_search") == "low"
        assert _get_impact_level("unknown") == "low"


# ---------------------------------------------------------------------------
# Tool safety classification
# ---------------------------------------------------------------------------


class TestToolSafety:
    def test_shell_requires_confirmation(self):
        from timmy.tool_safety import requires_confirmation

        assert requires_confirmation("shell") is True

    def test_python_requires_confirmation(self):
        from timmy.tool_safety import requires_confirmation

        assert requires_confirmation("python") is True

    def test_write_file_requires_confirmation(self):
        from timmy.tool_safety import requires_confirmation

        assert requires_confirmation("write_file") is True

    def test_read_file_is_safe(self):
        from timmy.tool_safety import requires_confirmation

        assert requires_confirmation("read_file") is False

    def test_calculator_is_safe(self):
        from timmy.tool_safety import requires_confirmation

        assert requires_confirmation("calculator") is False

    def test_web_search_is_safe(self):
        from timmy.tool_safety import requires_confirmation

        assert requires_confirmation("web_search") is False

    def test_unknown_tool_requires_confirmation(self):
        from timmy.tool_safety import requires_confirmation

        assert requires_confirmation("unknown_tool") is True


# ---------------------------------------------------------------------------
# _handle_message confirmation flow (native Agno RunOutput)
# ---------------------------------------------------------------------------


def _mock_paused_run(tool_name="shell", tool_args=None, content="I will create the dir."):
    """Create a mock RunOutput for a paused run needing tool confirmation."""
    tool_args = tool_args or {"args": ["mkdir", "/tmp/test"]}

    te = MagicMock()
    te.tool_name = tool_name
    te.tool_args = tool_args

    req = MagicMock()
    req.needs_confirmation = True
    req.tool_execution = te

    run = MagicMock()
    run.content = content
    run.status = "PAUSED"
    run.active_requirements = [req]
    return run


def _mock_completed_run(content="Hello! How can I help?"):
    """Create a mock RunOutput for a completed (no tool) run."""
    run = MagicMock()
    run.content = content
    run.status = "COMPLETED"
    run.active_requirements = []
    return run


class TestHandleMessageConfirmation:
    @pytest.mark.asyncio
    async def test_dangerous_tool_sends_confirmation(self, monkeypatch):
        """When Agno pauses for tool confirmation, should send confirmation prompt."""
        from integrations.chat_bridge.vendors.discord import DiscordVendor

        vendor = DiscordVendor()

        # Mock chat_with_tools returning a paused RunOutput
        paused_run = _mock_paused_run()
        monkeypatch.setattr(
            "integrations.chat_bridge.vendors.discord.chat_with_tools",
            lambda msg, sid=None: paused_run,
        )

        vendor._client = MagicMock()
        vendor._client.user = MagicMock()
        vendor._client.user.id = 12345

        message = MagicMock()
        message.content = "create a directory"
        message.channel = MagicMock()
        message.channel.guild = MagicMock()

        monkeypatch.setattr(vendor, "_get_or_create_thread", AsyncMock(return_value=None))

        ctx = AsyncMock()
        ctx.__aenter__ = AsyncMock(return_value=None)
        ctx.__aexit__ = AsyncMock(return_value=False)
        message.channel.typing = MagicMock(return_value=ctx)
        message.channel.send = AsyncMock()

        # Mock approvals
        mock_item = MagicMock()
        mock_item.id = "test-approval-id-1234"
        monkeypatch.setattr(
            "timmy.approvals.create_item",
            lambda **kwargs: mock_item,
        )

        vendor._send_confirmation = AsyncMock()

        await vendor._handle_message(message)

        # Should have called _send_confirmation for the shell tool
        vendor._send_confirmation.assert_called_once()
        call_args = vendor._send_confirmation.call_args
        assert call_args[0][1] == "shell"  # tool_name
        assert call_args[0][3] == "test-approval-id-1234"  # approval_id

    @pytest.mark.asyncio
    async def test_no_tool_calls_sends_normal_response(self, monkeypatch):
        """When Agno returns a completed run, should send text directly."""
        from integrations.chat_bridge.vendors.discord import DiscordVendor

        vendor = DiscordVendor()

        completed_run = _mock_completed_run()
        monkeypatch.setattr(
            "integrations.chat_bridge.vendors.discord.chat_with_tools",
            lambda msg, sid=None: completed_run,
        )

        vendor._client = MagicMock()
        vendor._client.user = MagicMock()
        vendor._client.user.id = 12345

        message = MagicMock()
        message.content = "hello"
        message.channel = MagicMock()
        message.channel.guild = MagicMock()
        monkeypatch.setattr(vendor, "_get_or_create_thread", AsyncMock(return_value=None))

        ctx = AsyncMock()
        ctx.__aenter__ = AsyncMock(return_value=None)
        ctx.__aexit__ = AsyncMock(return_value=False)
        message.channel.typing = MagicMock(return_value=ctx)
        message.channel.send = AsyncMock()

        await vendor._handle_message(message)

        # Should send the text response directly (no confirmation)
        message.channel.send.assert_called()
        sent_text = message.channel.send.call_args_list[-1][0][0]
        assert "Hello" in sent_text

    @pytest.mark.asyncio
    async def test_confirmation_disabled_via_config(self, monkeypatch):
        """When discord_confirm_actions=False, no confirmation prompts sent."""
        from config import settings
        from integrations.chat_bridge.vendors.discord import DiscordVendor

        monkeypatch.setattr(settings, "discord_confirm_actions", False)

        vendor = DiscordVendor()

        paused_run = _mock_paused_run()
        monkeypatch.setattr(
            "integrations.chat_bridge.vendors.discord.chat_with_tools",
            lambda msg, sid=None: paused_run,
        )

        vendor._client = MagicMock()
        vendor._client.user = MagicMock()
        vendor._client.user.id = 12345

        message = MagicMock()
        message.content = "do something"
        message.channel = MagicMock()
        message.channel.guild = MagicMock()
        monkeypatch.setattr(vendor, "_get_or_create_thread", AsyncMock(return_value=None))

        ctx = AsyncMock()
        ctx.__aenter__ = AsyncMock(return_value=None)
        ctx.__aexit__ = AsyncMock(return_value=False)
        message.channel.typing = MagicMock(return_value=ctx)
        message.channel.send = AsyncMock()

        vendor._send_confirmation = AsyncMock()

        await vendor._handle_message(message)

        # Should NOT call _send_confirmation
        vendor._send_confirmation.assert_not_called()
