"""Tests for ReminderDispatcher (U5)."""

from __future__ import annotations

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

from agentkit.calendar.models import CalendarEvent, _now_iso
from agentkit.calendar.reminders import ReminderDispatcher, SmtpConfig


def _make_event() -> CalendarEvent:
    now = _now_iso()
    return CalendarEvent(
        id="evt-1",
        user_id="user-1",
        title="Test Meeting",
        start_time="2026-07-01T10:00:00+00:00",
        end_time="2026-07-01T11:00:00+00:00",
        last_modified=now,
        created_at=now,
    )


# ---------------------------------------------------------------------------
# Client channel
# ---------------------------------------------------------------------------


async def test_client_channel_sends_ws_message() -> None:
    """Mock WS sender callback, verify called with correct payload."""
    ws_sender = AsyncMock()
    dispatcher = ReminderDispatcher(ws_sender=ws_sender)

    event = _make_event()
    result = await dispatcher.dispatch("client", event, "user-1")

    assert result is True
    ws_sender.assert_called_once()
    call_args = ws_sender.call_args
    assert call_args.args[0] == "user-1"
    assert call_args.args[1]["type"] == "calendar_reminder"
    assert call_args.args[1]["data"]["title"] == "Test Meeting"


async def test_client_channel_returns_false_without_sender() -> None:
    """No ws_sender configured → returns False."""
    dispatcher = ReminderDispatcher()
    result = await dispatcher.dispatch("client", _make_event(), "user-1")
    assert result is False


# ---------------------------------------------------------------------------
# Email channel
# ---------------------------------------------------------------------------


async def test_email_channel_sends_smtp() -> None:
    """Mock aiosmtplib via sys.modules injection, verify send called."""
    mock_aiosmtplib = MagicMock()
    mock_aiosmtplib.send = AsyncMock()

    with patch.dict(sys.modules, {"aiosmtplib": mock_aiosmtplib}):
        dispatcher = ReminderDispatcher(
            smtp_config=SmtpConfig(host="smtp.example.com", port=587),
            get_user_email=AsyncMock(return_value="user@example.com"),
        )
        event = _make_event()
        result = await dispatcher.dispatch("email", event, "user-1")

        assert result is True
        mock_aiosmtplib.send.assert_called_once()
        call_kwargs = mock_aiosmtplib.send.call_args.kwargs
        assert call_kwargs["hostname"] == "smtp.example.com"
        assert call_kwargs["port"] == 587
        # Message body contains event title and recipient
        message_body = mock_aiosmtplib.send.call_args.args[0]
        assert "user@example.com" in message_body
        assert "Test Meeting" in message_body


async def test_email_channel_returns_false_without_config() -> None:
    """No smtp_config → returns False."""
    dispatcher = ReminderDispatcher()
    result = await dispatcher.dispatch("email", _make_event(), "user-1")
    assert result is False


async def test_email_channel_returns_false_when_user_has_no_email() -> None:
    """get_user_email returns None → returns False."""
    dispatcher = ReminderDispatcher(
        smtp_config=SmtpConfig(),
        get_user_email=AsyncMock(return_value=None),
    )
    result = await dispatcher.dispatch("email", _make_event(), "user-1")
    assert result is False


# ---------------------------------------------------------------------------
# Webhook channel
# ---------------------------------------------------------------------------


async def test_webhook_channel_posts_to_url() -> None:
    """Mock httpx.AsyncClient, verify POST called with event payload."""
    dispatcher = ReminderDispatcher(webhook_url="https://example.com/hook")

    mock_response = MagicMock()
    mock_response.status_code = 200

    mock_client = AsyncMock()
    mock_client.post = AsyncMock(return_value=mock_response)
    mock_client.__aenter__.return_value = mock_client
    mock_client.__aexit__.return_value = None

    with patch("httpx.AsyncClient", return_value=mock_client):
        event = _make_event()
        result = await dispatcher.dispatch("webhook", event, "user-1")

        assert result is True
        mock_client.post.assert_called_once()
        call_kwargs = mock_client.post.call_args.kwargs
        assert call_kwargs["json"]["event"]["title"] == "Test Meeting"
        assert call_kwargs["json"]["user_id"] == "user-1"


async def test_webhook_channel_returns_false_on_4xx() -> None:
    """Webhook returns 500 → returns False."""
    dispatcher = ReminderDispatcher(webhook_url="https://example.com/hook")

    mock_response = MagicMock()
    mock_response.status_code = 500

    mock_client = AsyncMock()
    mock_client.post = AsyncMock(return_value=mock_response)
    mock_client.__aenter__.return_value = mock_client
    mock_client.__aexit__.return_value = None

    with patch("httpx.AsyncClient", return_value=mock_client):
        result = await dispatcher.dispatch("webhook", _make_event(), "user-1")
        assert result is False


async def test_webhook_channel_returns_false_without_url() -> None:
    """No webhook_url configured → returns False."""
    dispatcher = ReminderDispatcher()
    result = await dispatcher.dispatch("webhook", _make_event(), "user-1")
    assert result is False


# ---------------------------------------------------------------------------
# Unknown channel
# ---------------------------------------------------------------------------


async def test_unknown_channel_returns_false() -> None:
    """Unknown channel name → returns False, no crash."""
    dispatcher = ReminderDispatcher()
    result = await dispatcher.dispatch("sms", _make_event(), "user-1")
    assert result is False
