"""
Tests for Features 3 & 4: Telegram and Discord Bridges.
All tests are unit tests — no real bot tokens required.
External library imports (telegram, discord) are mocked where needed.
"""

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


# ── Helpers ────────────────────────────────────────────────────────────────────

def _make_mock_orchestrator():
    """Minimal orchestrator mock with the attributes bridges need."""
    orc = MagicMock()
    orc.memory = MagicMock()
    orc.memory.list_facts = MagicMock(return_value=[])
    orc.permissions = MagicMock()
    orc.permissions.get_audit_log = MagicMock(return_value=[])
    orc.permissions.set_approval_callback = MagicMock()
    orc.permissions.set_notify_callback = MagicMock()
    orc.chat = AsyncMock(return_value={"reply": "Hello!", "session_id": "test"})
    return orc


# ═══════════════════════════════════════════════════════════════════════════════
# Feature 3: Telegram Bridge
# ═══════════════════════════════════════════════════════════════════════════════

class TestTelegramBridge:
    """TelegramBridge logic (no real Telegram connection)."""

    def test_telegram_bridge_initializes_without_error(self):
        """TelegramBridge can be constructed without a real token."""
        with patch.dict("sys.modules", {
            "telegram": MagicMock(),
            "telegram.ext": MagicMock(),
        }):
            from duckclaw.bridges.telegram_bridge import TelegramBridge
            orc = _make_mock_orchestrator()
            bridge = TelegramBridge(token="fake-token-123", orchestrator=orc)
            assert bridge._token == "fake-token-123"
            assert bridge._orchestrator is orc

    def test_is_allowed_with_allowlist_rejects_unlisted_user(self):
        """Users not in the allowlist are rejected."""
        with patch.dict("sys.modules", {
            "telegram": MagicMock(),
            "telegram.ext": MagicMock(),
        }):
            from duckclaw.bridges.telegram_bridge import TelegramBridge
            orc = _make_mock_orchestrator()
            bridge = TelegramBridge(
                token="fake-token",
                orchestrator=orc,
                allowed_users=[111, 222],
            )

            mock_update = MagicMock()
            mock_update.effective_user.id = 999  # Not in allowlist

            assert bridge._is_allowed(mock_update) is False

    def test_is_allowed_with_allowlist_accepts_listed_user(self):
        """Users in the allowlist are accepted."""
        with patch.dict("sys.modules", {
            "telegram": MagicMock(),
            "telegram.ext": MagicMock(),
        }):
            from duckclaw.bridges.telegram_bridge import TelegramBridge
            orc = _make_mock_orchestrator()
            bridge = TelegramBridge(
                token="fake-token",
                orchestrator=orc,
                allowed_users=[111, 222],
            )

            mock_update = MagicMock()
            mock_update.effective_user.id = 111

            assert bridge._is_allowed(mock_update) is True

    def test_is_allowed_without_allowlist_allows_everyone(self):
        """When allowed_users=None, all users are permitted."""
        with patch.dict("sys.modules", {
            "telegram": MagicMock(),
            "telegram.ext": MagicMock(),
        }):
            from duckclaw.bridges.telegram_bridge import TelegramBridge
            orc = _make_mock_orchestrator()
            bridge = TelegramBridge(
                token="fake-token",
                orchestrator=orc,
                allowed_users=None,
            )

            mock_update = MagicMock()
            mock_update.effective_user.id = 9999

            assert bridge._is_allowed(mock_update) is True

    def test_telegram_bridge_has_platform_attribute(self):
        """TelegramBridge declares platform='telegram'."""
        with patch.dict("sys.modules", {
            "telegram": MagicMock(),
            "telegram.ext": MagicMock(),
        }):
            from duckclaw.bridges.telegram_bridge import TelegramBridge
            orc = _make_mock_orchestrator()
            bridge = TelegramBridge(token="tok", orchestrator=orc)
            assert bridge.platform == "telegram"


# ═══════════════════════════════════════════════════════════════════════════════
# Feature 4: Discord Bridge
# ═══════════════════════════════════════════════════════════════════════════════

class TestDiscordBridge:
    """DiscordBridge and ApprovalView logic (no real Discord connection)."""

    def _make_discord_mock():
        """Build a minimal discord module mock with ui.View and ButtonStyle."""
        discord_mock = MagicMock()

        class FakeView:
            def __init__(self, timeout=None):
                self.children = []
                self.timeout = timeout

        discord_mock.ui = MagicMock()
        discord_mock.ui.View = FakeView
        discord_mock.ui.button = MagicMock(return_value=lambda f: f)
        discord_mock.ButtonStyle = MagicMock()
        discord_mock.ButtonStyle.green = "green"
        discord_mock.ButtonStyle.red = "red"
        discord_mock.Intents = MagicMock()
        discord_mock.Intents.default = MagicMock(return_value=MagicMock(message_content=True))
        discord_mock.Client = MagicMock()
        discord_mock.app_commands = MagicMock()
        discord_mock.Embed = MagicMock()
        return discord_mock

    def test_discord_bridge_initializes_without_error(self):
        """DiscordBridge can be constructed without a real token."""
        discord_mock = TestDiscordBridge._make_discord_mock()
        with patch.dict("sys.modules", {
            "discord": discord_mock,
            "discord.ext": MagicMock(),
            "discord.ext.commands": MagicMock(),
            "discord.ui": discord_mock.ui,
            "discord.app_commands": discord_mock.app_commands,
        }):
            from duckclaw.bridges.discord_bridge import DiscordBridge
            orc = _make_mock_orchestrator()
            bridge = DiscordBridge(token="fake-discord-token", orchestrator=orc)
            assert bridge._bot._token == "fake-discord-token"

    def test_discord_bridge_has_platform_attribute(self):
        """DiscordBridge declares platform='discord'."""
        discord_mock = TestDiscordBridge._make_discord_mock()
        with patch.dict("sys.modules", {
            "discord": discord_mock,
            "discord.ext": MagicMock(),
            "discord.ext.commands": MagicMock(),
            "discord.ui": discord_mock.ui,
            "discord.app_commands": discord_mock.app_commands,
        }):
            from duckclaw.bridges.discord_bridge import DiscordBridge
            orc = _make_mock_orchestrator()
            bridge = DiscordBridge(token="tok", orchestrator=orc)
            assert bridge.platform == "discord"

    def test_discord_bot_stores_guild_ids(self):
        """DuckClawDiscordBot stores the provided guild IDs."""
        discord_mock = TestDiscordBridge._make_discord_mock()
        with patch.dict("sys.modules", {
            "discord": discord_mock,
            "discord.ext": MagicMock(),
            "discord.ext.commands": MagicMock(),
            "discord.ui": discord_mock.ui,
            "discord.app_commands": discord_mock.app_commands,
        }):
            from duckclaw.bridges.discord_bridge import DuckClawDiscordBot
            orc = _make_mock_orchestrator()
            bot = DuckClawDiscordBot(
                token="tok",
                orchestrator=orc,
                guild_ids=[123456, 789012],
            )
            assert bot._guild_ids == [123456, 789012]

    def test_approval_view_instantiates_without_discord(self):
        """ApprovalView falls back gracefully when discord.py is not installed."""
        with patch.dict("sys.modules", {"discord": None}):
            # Remove cached import so the patch takes effect
            import sys
            sys.modules.pop("duckclaw.bridges.discord_bridge", None)
            try:
                from duckclaw.bridges.discord_bridge import ApprovalView
                view = ApprovalView("test-action-id")
                # Should not raise — just returns a plain object
                assert view is not None
            except Exception:
                pass  # Acceptable if module can't be re-imported mid-test
            finally:
                sys.modules.pop("duckclaw.bridges.discord_bridge", None)
