"""Tests for NotificationService push notification integration."""

import uuid
from datetime import datetime, timedelta
from unittest.mock import AsyncMock, patch

import pytest
from preloop.models import models
from preloop.models.crud import crud_account, crud_user, notification_preferences

from preloop.services.notification_service import NotificationService

pytestmark = pytest.mark.asyncio


@pytest.fixture
def test_account(db_session):
    """Create test account."""
    account_data = {
        "organization_name": "Test Organization",
        "is_active": True,
    }
    account = crud_account.create(db_session, obj_in=account_data)
    db_session.commit()
    return account


@pytest.fixture
def test_user_with_ios_token(db_session, test_account):
    """Create test user with iOS notification preferences."""
    user_data = {
        "account_id": test_account.id,
        "email": "test@example.com",
        "username": "testuser",
        "full_name": "Test User",
        "is_active": True,
    }
    user = crud_user.create(db_session, obj_in=user_data)
    db_session.commit()

    # Create notification preferences with iOS token
    prefs = notification_preferences.get_or_create(db_session, user.id)
    prefs.enable_mobile_push = True
    prefs.add_device_token("ios", "a" * 64)
    db_session.commit()

    return user


@pytest.fixture
def test_user_without_push(db_session, test_account):
    """Create test user without push notifications enabled."""
    user_data = {
        "account_id": test_account.id,
        "email": "no-push@example.com",
        "username": "nopushuser",
        "full_name": "No Push User",
        "is_active": True,
    }
    user = crud_user.create(db_session, obj_in=user_data)
    db_session.commit()

    # Create notification preferences but disable push
    prefs = notification_preferences.get_or_create(db_session, user.id)
    prefs.enable_mobile_push = False
    db_session.commit()

    return user


@pytest.fixture
def test_approval_request(test_account):
    """Create test approval request."""
    return models.ApprovalRequest(
        id=uuid.uuid4(),
        account_id=str(test_account.id),
        tool_configuration_id=uuid.uuid4(),
        approval_workflow_id=uuid.uuid4(),
        tool_name="create_issue",
        tool_args={"title": "Test Issue"},
        agent_reasoning="Need to create an issue for testing",
        status="pending",
        requested_at=datetime.utcnow(),
        expires_at=datetime.utcnow() + timedelta(minutes=5),
    )


@pytest.fixture
def mock_apns_service():
    """Mock APNs service."""
    mock_service = AsyncMock()
    mock_service.send_notification = AsyncMock(return_value=(True, 200, None))
    return mock_service


class TestSendPushNotifications:
    """Test _send_push_notifications method."""

    async def test_send_to_single_user_with_ios_token(
        self,
        db_session,
        test_user_with_ios_token,
        test_approval_request,
        mock_apns_service,
    ):
        """Test sending push notification to single user with iOS token."""
        notification_service = NotificationService(db_session)

        with patch(
            "preloop.services.push_notifications.get_apns_service",
            return_value=mock_apns_service,
        ):
            result = await notification_service._send_push_notifications(
                test_approval_request, [test_user_with_ios_token.id]
            )

            assert result["success"] is True
            assert result["sent"] == 1
            assert result["failed"] == 0
            assert result["invalid_tokens_removed"] == 0

            # Verify send_notification was called
            mock_apns_service.send_notification.assert_called_once()
            call_kwargs = mock_apns_service.send_notification.call_args[1]
            assert call_kwargs["device_token"] == "a" * 64

    async def test_send_to_multiple_users(
        self, db_session, test_account, test_approval_request, mock_apns_service
    ):
        """Test sending push notifications to multiple users."""
        # Create multiple users with iOS tokens
        user1_data = {
            "account_id": test_account.id,
            "email": "user1@example.com",
            "username": "user1",
            "full_name": "User 1",
            "is_active": True,
        }
        user1 = crud_user.create(db_session, obj_in=user1_data)

        user2_data = {
            "account_id": test_account.id,
            "email": "user2@example.com",
            "username": "user2",
            "full_name": "User 2",
            "is_active": True,
        }
        user2 = crud_user.create(db_session, obj_in=user2_data)
        db_session.commit()

        for user in [user1, user2]:
            prefs = notification_preferences.get_or_create(db_session, user.id)
            prefs.enable_mobile_push = True
            prefs.add_device_token("ios", f"{'b' * 64}_{user.id}")
        db_session.commit()

        notification_service = NotificationService(db_session)

        with patch(
            "preloop.services.push_notifications.get_apns_service",
            return_value=mock_apns_service,
        ):
            result = await notification_service._send_push_notifications(
                test_approval_request, [user1.id, user2.id]
            )

            assert result["success"] is True
            assert result["sent"] == 2
            assert result["failed"] == 0

            # Verify send_notification was called twice
            assert mock_apns_service.send_notification.call_count == 2

    async def test_send_to_user_with_multiple_devices(
        self, db_session, test_account, test_approval_request, mock_apns_service
    ):
        """Test sending to user with multiple iOS devices."""
        user_data = {
            "account_id": test_account.id,
            "email": "multi-device@example.com",
            "username": "multideviceuser",
            "full_name": "Multi Device User",
            "is_active": True,
        }
        user = crud_user.create(db_session, obj_in=user_data)
        db_session.commit()

        prefs = notification_preferences.get_or_create(db_session, user.id)
        prefs.enable_mobile_push = True
        prefs.add_device_token("ios", "c" * 64)
        prefs.add_device_token("ios", "d" * 64)  # Second device (replaces first)
        db_session.commit()

        notification_service = NotificationService(db_session)

        with patch(
            "preloop.services.push_notifications.get_apns_service",
            return_value=mock_apns_service,
        ):
            result = await notification_service._send_push_notifications(
                test_approval_request, [user.id]
            )

            # Should only send to one device (latest registered)
            assert result["sent"] >= 1

    async def test_skip_user_without_mobile_push_enabled(
        self,
        db_session,
        test_user_without_push,
        test_approval_request,
        mock_apns_service,
    ):
        """Test that users without mobile push enabled are skipped."""
        notification_service = NotificationService(db_session)

        with patch(
            "preloop.services.push_notifications.get_apns_service",
            return_value=mock_apns_service,
        ):
            result = await notification_service._send_push_notifications(
                test_approval_request, [test_user_without_push.id]
            )

            assert result["sent"] == 0
            # send_notification should not be called
            mock_apns_service.send_notification.assert_not_called()

    async def test_skip_user_without_ios_tokens(
        self, db_session, test_account, test_approval_request, mock_apns_service
    ):
        """Test that users without iOS tokens are skipped."""
        user_data = {
            "account_id": test_account.id,
            "email": "android-only@example.com",
            "username": "androiduser",
            "full_name": "Android User",
            "is_active": True,
        }
        user = crud_user.create(db_session, obj_in=user_data)
        db_session.commit()

        prefs = notification_preferences.get_or_create(db_session, user.id)
        prefs.enable_mobile_push = True
        prefs.add_device_token("android", "android_token_123")
        db_session.commit()

        notification_service = NotificationService(db_session)

        with patch(
            "preloop.services.push_notifications.get_apns_service",
            return_value=mock_apns_service,
        ):
            result = await notification_service._send_push_notifications(
                test_approval_request, [user.id]
            )

            assert result["sent"] == 0
            # send_notification should not be called (no iOS tokens)
            mock_apns_service.send_notification.assert_not_called()

    async def test_invalid_token_removal_410(
        self,
        db_session,
        test_user_with_ios_token,
        test_approval_request,
        mock_apns_service,
    ):
        """Test that invalid tokens (410 response) are removed from database."""
        # Mock 410 response (invalid token)
        mock_apns_service.send_notification = AsyncMock(
            return_value=(False, 410, "Unregistered")
        )

        notification_service = NotificationService(db_session)

        with patch(
            "preloop.services.push_notifications.get_apns_service",
            return_value=mock_apns_service,
        ):
            result = await notification_service._send_push_notifications(
                test_approval_request, [test_user_with_ios_token.id]
            )

            assert result["success"] is True  # No failures, just invalid tokens
            assert result["sent"] == 0
            assert result["invalid_tokens_removed"] == 1

            # Verify token was removed from database
            prefs = notification_preferences.get_by_user(
                db_session, test_user_with_ios_token.id
            )
            assert len(prefs.get_device_tokens(platform="ios")) == 0

    async def test_escalation_notification(
        self,
        db_session,
        test_user_with_ios_token,
        test_approval_request,
        mock_apns_service,
    ):
        """Test that escalation notifications have correct title prefix."""
        notification_service = NotificationService(db_session)

        with patch(
            "preloop.services.push_notifications.get_apns_service",
            return_value=mock_apns_service,
        ):
            result = await notification_service._send_push_notifications(
                test_approval_request,
                [test_user_with_ios_token.id],
                is_escalation=True,
            )

            assert result["is_escalation"] is True
            assert result["sent"] == 1

            # Verify payload has escalation prefix
            call_kwargs = mock_apns_service.send_notification.call_args[1]
            payload = call_kwargs["payload"]
            assert "ESCALATION:" in payload["aps"]["alert"]["title"]

    async def test_priority_mapping(
        self,
        db_session,
        test_user_with_ios_token,
        test_approval_request,
        mock_apns_service,
    ):
        """Test that escalation flag maps to APNs priority correctly."""
        notification_service = NotificationService(db_session)

        # Test escalation -> APNs priority 10 (high priority)
        with patch(
            "preloop.services.push_notifications.get_apns_service",
            return_value=mock_apns_service,
        ):
            await notification_service._send_push_notifications(
                test_approval_request, [test_user_with_ios_token.id], is_escalation=True
            )

            call_kwargs = mock_apns_service.send_notification.call_args[1]
            assert call_kwargs["priority"] == 10

    async def test_apns_not_configured(
        self, db_session, test_user_with_ios_token, test_approval_request
    ):
        """Test graceful handling when APNs is not configured."""
        notification_service = NotificationService(db_session)

        with patch(
            "preloop.services.push_notifications.get_apns_service",
            return_value=None,
        ):
            result = await notification_service._send_push_notifications(
                test_approval_request, [test_user_with_ios_token.id]
            )

            assert result["success"] is False
            assert result["error"] == "APNs not configured"
            assert result["sent"] == 0
            assert result["failed"] == 0

    async def test_send_failure_handling(
        self,
        db_session,
        test_user_with_ios_token,
        test_approval_request,
        mock_apns_service,
    ):
        """Test handling of send failures (non-410 errors)."""
        # Mock 500 response (server error)
        mock_apns_service.send_notification = AsyncMock(
            return_value=(False, 500, "InternalServerError")
        )

        notification_service = NotificationService(db_session)

        with patch(
            "preloop.services.push_notifications.get_apns_service",
            return_value=mock_apns_service,
        ):
            result = await notification_service._send_push_notifications(
                test_approval_request, [test_user_with_ios_token.id]
            )

            assert result["success"] is False
            assert result["sent"] == 0
            assert result["failed"] == 1
            assert result["invalid_tokens_removed"] == 0

            # Token should NOT be removed (not a 410)
            prefs = notification_preferences.get_by_user(
                db_session, test_user_with_ios_token.id
            )
            assert len(prefs.get_device_tokens(platform="ios")) == 1

    async def test_network_exception_handling(
        self,
        db_session,
        test_user_with_ios_token,
        test_approval_request,
        mock_apns_service,
    ):
        """Test handling of network exceptions."""
        # Mock exception
        mock_apns_service.send_notification = AsyncMock(
            side_effect=Exception("Network error")
        )

        notification_service = NotificationService(db_session)

        with patch(
            "preloop.services.push_notifications.get_apns_service",
            return_value=mock_apns_service,
        ):
            result = await notification_service._send_push_notifications(
                test_approval_request, [test_user_with_ios_token.id]
            )

            assert result["success"] is False
            assert result["sent"] == 0
            assert result["failed"] == 1
