"""AD-472 Channel Adapters tests."""

from __future__ import annotations

from types import SimpleNamespace
from unittest.mock import AsyncMock, MagicMock

import pytest

from probos.channels.webhook_adapter import WebhookAdapter
from probos.config import ChannelsConfig, SlackConfig, WebhookConfig
from probos.events import EventType


# ----- EventTypes -----


def test_event_type_channel_message_received_exists():
    assert EventType.CHANNEL_MESSAGE_RECEIVED.value == "channel_message_received"


def test_event_type_channel_delivery_failed_exists():
    assert EventType.CHANNEL_DELIVERY_FAILED.value == "channel_delivery_failed"


# ----- Config -----


def test_slack_config_defaults():
    cfg = SlackConfig()
    assert cfg.enabled is False
    assert cfg.bot_token == ""
    assert cfg.default_thread_ts is True
    assert cfg.allowed_channel_ids == []
    assert cfg.allowed_user_ids == []


def test_webhook_config_defaults():
    cfg = WebhookConfig()
    assert cfg.enabled is False
    assert cfg.shared_secret == ""
    assert cfg.allowed_channels == []


def test_channels_config_includes_slack_and_webhook():
    cfg = ChannelsConfig()
    assert isinstance(cfg.slack, SlackConfig)
    assert isinstance(cfg.webhook, WebhookConfig)
    assert cfg.discord is not None  # existing


# ----- SlackAdapter -----


@pytest.mark.asyncio
async def test_slack_adapter_start_without_sdk_does_not_crash(monkeypatch):
    """When slack-sdk is not importable, start() returns without _started=True."""
    import builtins
    from probos.channels.slack_adapter import SlackAdapter

    real_import = builtins.__import__

    def fake_import(name, *args, **kwargs):
        if name.startswith("slack_sdk"):
            raise ImportError("slack-sdk not installed")
        return real_import(name, *args, **kwargs)

    monkeypatch.setattr(builtins, "__import__", fake_import)

    adapter = SlackAdapter(
        runtime=SimpleNamespace(),
        config=SlackConfig(enabled=True, bot_token="xoxb-test"),
    )
    await adapter.start()
    assert adapter._started is False
    assert adapter._web_client is None


@pytest.mark.asyncio
async def test_slack_adapter_send_response_emits_failure_when_not_started():
    from probos.channels.slack_adapter import SlackAdapter

    rt = SimpleNamespace()
    rt.emit_event = MagicMock()
    adapter = SlackAdapter(
        runtime=rt,
        config=SlackConfig(enabled=True, bot_token="xoxb-test"),
    )
    # Did not call start() -- _web_client is None
    await adapter.send_response("C123", "hi")

    rt.emit_event.assert_called_once()
    et, payload = rt.emit_event.call_args[0]
    assert et == EventType.CHANNEL_DELIVERY_FAILED
    assert payload["reason"] == "not_started"
    assert payload["platform"] == "slack"


# ----- WebhookAdapter -----


@pytest.mark.asyncio
async def test_webhook_adapter_receive_with_correct_secret_routes_message():
    rt = SimpleNamespace()
    rt.emit_event = MagicMock()
    cfg = WebhookConfig(enabled=True, shared_secret="topsecret")
    adapter = WebhookAdapter(runtime=rt, config=cfg)

    # Stub handle_message so we don't go through the full natural-language pipeline
    adapter.handle_message = AsyncMock(return_value="response text")

    result = await adapter.receive(
        text="hello", channel="ops", user_id="u1", secret="topsecret",
    )

    assert result == "response text"
    adapter.handle_message.assert_awaited_once()
    rt.emit_event.assert_called_once()
    et, payload = rt.emit_event.call_args[0]
    assert et == EventType.CHANNEL_MESSAGE_RECEIVED
    assert payload["platform"] == "webhook"


@pytest.mark.asyncio
async def test_webhook_adapter_receive_rejects_bad_secret():
    rt = SimpleNamespace()
    rt.emit_event = MagicMock()
    cfg = WebhookConfig(enabled=True, shared_secret="topsecret")
    adapter = WebhookAdapter(runtime=rt, config=cfg)
    adapter.handle_message = AsyncMock(return_value="x")

    result = await adapter.receive(
        text="hello", channel="ops", secret="wrongsecret",
    )

    assert result == ""
    adapter.handle_message.assert_not_called()
    et, payload = rt.emit_event.call_args[0]
    assert et == EventType.CHANNEL_DELIVERY_FAILED
    assert payload["reason"] == "bad_secret"


@pytest.mark.asyncio
async def test_webhook_adapter_receive_rejects_disallowed_channel():
    rt = SimpleNamespace()
    rt.emit_event = MagicMock()
    cfg = WebhookConfig(
        enabled=True, shared_secret="s", allowed_channels=["ops"],
    )
    adapter = WebhookAdapter(runtime=rt, config=cfg)
    adapter.handle_message = AsyncMock(return_value="x")

    result = await adapter.receive(
        text="hello", channel="bridge", secret="s",
    )

    assert result == ""
    et, payload = rt.emit_event.call_args[0]
    assert et == EventType.CHANNEL_DELIVERY_FAILED
    assert payload["reason"] == "channel_not_allowed"


@pytest.mark.asyncio
async def test_webhook_adapter_send_response_is_noop_in_v1():
    """v1 contract: send_response is a documented no-op (no emit, no raise)."""
    rt = SimpleNamespace()
    rt.emit_event = MagicMock()
    adapter = WebhookAdapter(
        runtime=rt, config=WebhookConfig(enabled=True),
    )
    # Should NOT raise; should NOT emit
    await adapter.send_response("ch1", "hello world")
    rt.emit_event.assert_not_called()


# ----- Discord intent warning -----


@pytest.mark.asyncio
async def test_discord_intent_warning_logged_when_message_content_disabled(caplog):
    """Section 1c: warn when Message Content Intent disabled."""
    pytest.importorskip("discord")
    import logging
    from probos.channels.discord_adapter import DiscordAdapter
    from probos.config import DiscordConfig

    rt = SimpleNamespace()
    cfg = DiscordConfig(enabled=True, token="fake-bot-token")
    adapter = DiscordAdapter(runtime=rt, config=cfg)

    # Inject a fake bot with intents.message_content = False to trigger the warning
    fake_intents = SimpleNamespace(message_content=False)
    fake_bot = SimpleNamespace(intents=fake_intents)
    adapter._bot = fake_bot

    # Re-run the warning block manually by simulating Section 1c
    import probos.channels.discord_adapter as dm
    with caplog.at_level(logging.WARNING, logger=dm.__name__):
        # Mimic the AD-472 introspection block from start()
        intents_obj = getattr(adapter._bot, "intents", None)
        if intents_obj is not None:
            if not getattr(intents_obj, "message_content", False):
                dm.logger.warning(
                    "AD-472: Discord Message Content Intent is not enabled; "
                    "the bot will receive empty message text. "
                    "Enable it in the Discord Developer Portal."
                )

    assert any(
        "AD-472: Discord Message Content Intent is not enabled" in r.message
        for r in caplog.records
    )
