"""Unit tests for LoyaltyNotificationService."""

from unittest.mock import MagicMock, patch

import pytest

from app.modules.loyalty.services.notification_service import (
    LoyaltyNotificationService,
)


@pytest.fixture
def notification_service():
    return LoyaltyNotificationService()


def _make_card_stub(
    card_id=1,
    customer_id=10,
    merchant_id=100,
    enrolled_at_store_id=200,
    card_number="1234-5678-9012",
):
    """Lightweight card stub for notification tests."""
    card = MagicMock()
    card.id = card_id
    card.customer_id = customer_id
    card.merchant_id = merchant_id
    card.enrolled_at_store_id = enrolled_at_store_id
    card.card_number = card_number
    card.program = MagicMock()
    card.program.display_name = "Test Rewards"
    return card


def _make_customer_stub(email="test@example.com", full_name="Test User"):
    customer = MagicMock()
    customer.id = 10
    customer.email = email
    customer.full_name = full_name
    return customer


def _make_store_stub(name="Test Store"):
    store = MagicMock()
    store.id = 200
    store.name = name
    return store


@pytest.mark.unit
@pytest.mark.loyalty
class TestNotificationServiceDispatch:
    """Verify that each notification method enqueues the correct template."""

    @patch("app.modules.loyalty.tasks.notifications.send_notification_email")
    @patch("app.modules.tenancy.services.store_service.store_service")
    @patch("app.modules.customers.services.customer_service.customer_service")
    def test_send_enrollment_confirmation(
        self, mock_customer_svc, mock_store_svc, mock_task, notification_service
    ):
        mock_customer_svc.get_customer_by_id.return_value = _make_customer_stub()
        mock_store_svc.get_store_by_id_optional.return_value = _make_store_stub()
        db = MagicMock()
        card = _make_card_stub()

        notification_service.send_enrollment_confirmation(db, card)

        mock_task.delay.assert_called_once()
        call_kwargs = mock_task.delay.call_args[1]
        assert call_kwargs["template_code"] == "loyalty_enrollment"
        assert call_kwargs["to_email"] == "test@example.com"
        assert "card_number" in call_kwargs["variables"]

    @patch("app.modules.loyalty.tasks.notifications.send_notification_email")
    @patch("app.modules.tenancy.services.store_service.store_service")
    @patch("app.modules.customers.services.customer_service.customer_service")
    def test_send_welcome_bonus(
        self, mock_customer_svc, mock_store_svc, mock_task, notification_service
    ):
        mock_customer_svc.get_customer_by_id.return_value = _make_customer_stub()
        mock_store_svc.get_store_by_id_optional.return_value = _make_store_stub()
        db = MagicMock()
        card = _make_card_stub()

        notification_service.send_welcome_bonus(db, card, 50)

        mock_task.delay.assert_called_once()
        call_kwargs = mock_task.delay.call_args[1]
        assert call_kwargs["template_code"] == "loyalty_welcome_bonus"
        assert call_kwargs["variables"]["points"] == "50"

    @patch("app.modules.loyalty.tasks.notifications.send_notification_email")
    @patch("app.modules.tenancy.services.store_service.store_service")
    @patch("app.modules.customers.services.customer_service.customer_service")
    def test_send_welcome_bonus_skipped_for_zero_points(
        self, mock_customer_svc, mock_store_svc, mock_task, notification_service
    ):
        db = MagicMock()
        card = _make_card_stub()

        notification_service.send_welcome_bonus(db, card, 0)

        mock_task.delay.assert_not_called()

    @patch("app.modules.loyalty.tasks.notifications.send_notification_email")
    @patch("app.modules.tenancy.services.store_service.store_service")
    @patch("app.modules.customers.services.customer_service.customer_service")
    def test_send_points_expiration_warning(
        self, mock_customer_svc, mock_store_svc, mock_task, notification_service
    ):
        mock_customer_svc.get_customer_by_id.return_value = _make_customer_stub()
        mock_store_svc.get_store_by_id_optional.return_value = _make_store_stub()
        db = MagicMock()
        card = _make_card_stub()

        notification_service.send_points_expiration_warning(
            db, card, expiring_points=200, days_remaining=14, expiration_date="2026-05-01"
        )

        mock_task.delay.assert_called_once()
        call_kwargs = mock_task.delay.call_args[1]
        assert call_kwargs["template_code"] == "loyalty_points_expiring"
        assert call_kwargs["variables"]["points"] == "200"
        assert call_kwargs["variables"]["days_remaining"] == "14"

    @patch("app.modules.loyalty.tasks.notifications.send_notification_email")
    @patch("app.modules.tenancy.services.store_service.store_service")
    @patch("app.modules.customers.services.customer_service.customer_service")
    def test_send_points_expired(
        self, mock_customer_svc, mock_store_svc, mock_task, notification_service
    ):
        mock_customer_svc.get_customer_by_id.return_value = _make_customer_stub()
        mock_store_svc.get_store_by_id_optional.return_value = _make_store_stub()
        db = MagicMock()
        card = _make_card_stub()

        notification_service.send_points_expired(db, card, 150)

        mock_task.delay.assert_called_once()
        call_kwargs = mock_task.delay.call_args[1]
        assert call_kwargs["template_code"] == "loyalty_points_expired"
        assert call_kwargs["variables"]["expired_points"] == "150"

    @patch("app.modules.loyalty.tasks.notifications.send_notification_email")
    @patch("app.modules.tenancy.services.store_service.store_service")
    @patch("app.modules.customers.services.customer_service.customer_service")
    def test_send_reward_available(
        self, mock_customer_svc, mock_store_svc, mock_task, notification_service
    ):
        mock_customer_svc.get_customer_by_id.return_value = _make_customer_stub()
        mock_store_svc.get_store_by_id_optional.return_value = _make_store_stub()
        db = MagicMock()
        card = _make_card_stub()

        notification_service.send_reward_available(db, card, "Free Coffee")

        mock_task.delay.assert_called_once()
        call_kwargs = mock_task.delay.call_args[1]
        assert call_kwargs["template_code"] == "loyalty_reward_ready"
        assert call_kwargs["variables"]["reward_name"] == "Free Coffee"

    @patch("app.modules.loyalty.tasks.notifications.send_notification_email")
    @patch("app.modules.tenancy.services.store_service.store_service")
    @patch("app.modules.customers.services.customer_service.customer_service")
    def test_no_email_skips_dispatch(
        self, mock_customer_svc, mock_store_svc, mock_task, notification_service
    ):
        """Customer without email does not trigger a send."""
        no_email_customer = _make_customer_stub(email=None)
        mock_customer_svc.get_customer_by_id.return_value = no_email_customer
        db = MagicMock()
        card = _make_card_stub()

        notification_service.send_enrollment_confirmation(db, card)

        mock_task.delay.assert_not_called()

    @patch("app.modules.loyalty.tasks.notifications.send_notification_email")
    @patch("app.modules.tenancy.services.store_service.store_service")
    @patch("app.modules.customers.services.customer_service.customer_service")
    def test_customer_not_found_skips_dispatch(
        self, mock_customer_svc, mock_store_svc, mock_task, notification_service
    ):
        """Missing customer does not trigger a send."""
        mock_customer_svc.get_customer_by_id.return_value = None
        db = MagicMock()
        card = _make_card_stub()

        notification_service.send_enrollment_confirmation(db, card)

        mock_task.delay.assert_not_called()
