# tests/unit/services/test_admin_notification_service.py
"""
Unit tests for AdminNotificationService and PlatformAlertService.
"""

from datetime import datetime, timedelta

import pytest

from app.modules.messaging.services.admin_notification_service import (
    AdminNotificationService,
    AlertType,
    NotificationType,
    PlatformAlertService,
    Priority,
    Severity,
)


@pytest.fixture
def notification_service():
    """Create AdminNotificationService instance."""
    return AdminNotificationService()


@pytest.fixture
def alert_service():
    """Create PlatformAlertService instance."""
    return PlatformAlertService()


# ============================================================================
# ADMIN NOTIFICATION SERVICE TESTS
# ============================================================================


@pytest.mark.unit
class TestAdminNotificationServiceCreate:
    """Tests for notification creation."""

    def test_create_notification_basic(self, db, notification_service):
        """Test creating a basic notification."""
        notification = notification_service.create_notification(
            db=db,
            notification_type=NotificationType.SYSTEM_ALERT,
            title="Test Alert",
            message="This is a test notification.",
        )
        db.commit()

        assert notification.id is not None
        assert notification.type == NotificationType.SYSTEM_ALERT
        assert notification.title == "Test Alert"
        assert notification.message == "This is a test notification."
        assert notification.priority == Priority.NORMAL
        assert notification.action_required is False
        assert notification.is_read is False

    def test_create_notification_with_priority(self, db, notification_service):
        """Test creating notification with custom priority."""
        notification = notification_service.create_notification(
            db=db,
            notification_type=NotificationType.CRITICAL_ERROR,
            title="Critical Error",
            message="Something went wrong",
            priority=Priority.CRITICAL,
        )
        db.commit()

        assert notification.priority == Priority.CRITICAL

    def test_create_notification_with_action(self, db, notification_service):
        """Test creating notification with action URL."""
        notification = notification_service.create_notification(
            db=db,
            notification_type=NotificationType.IMPORT_FAILURE,
            title="Import Failed",
            message="Failed to import products",
            action_required=True,
            action_url="/admin/marketplace",
        )
        db.commit()

        assert notification.action_required is True
        assert notification.action_url == "/admin/marketplace"

    def test_create_notification_with_metadata(self, db, notification_service):
        """Test creating notification with metadata."""
        metadata = {"store_id": 1, "job_id": 42}
        notification = notification_service.create_notification(
            db=db,
            notification_type=NotificationType.IMPORT_FAILURE,
            title="Import Failed",
            message="Error message",
            metadata=metadata,
        )
        db.commit()

        assert notification.notification_metadata == metadata


@pytest.mark.unit
class TestAdminNotificationServiceConvenience:
    """Tests for convenience notification methods."""

    def test_notify_import_failure(self, db, notification_service):
        """Test import failure notification creation."""
        notification = notification_service.notify_import_failure(
            db=db,
            store_name="Test Store",
            job_id=123,
            error_message="Connection failed",
            store_id=1,
        )
        db.commit()

        assert notification.type == NotificationType.IMPORT_FAILURE
        assert notification.title == "Import Failed: Test Store"
        assert notification.message == "Connection failed"
        assert notification.priority == Priority.HIGH
        assert notification.action_required is True
        assert "store_id=1" in notification.action_url

    def test_notify_order_sync_failure(self, db, notification_service):
        """Test order sync failure notification."""
        notification = notification_service.notify_order_sync_failure(
            db=db,
            store_name="Test Store",
            error_message="API timeout",
            store_id=5,
        )
        db.commit()

        assert notification.type == NotificationType.ORDER_SYNC_FAILURE
        assert notification.title == "Order Sync Failed: Test Store"
        assert notification.priority == Priority.HIGH

    def test_notify_order_exception(self, db, notification_service):
        """Test order exception notification."""
        notification = notification_service.notify_order_exception(
            db=db,
            store_name="Test Store",
            order_number="ORD-12345",
            exception_count=3,
            store_id=2,
        )
        db.commit()

        assert notification.type == NotificationType.ORDER_EXCEPTION
        assert "3 item(s)" in notification.message
        assert notification.priority == Priority.NORMAL

    def test_notify_critical_error(self, db, notification_service):
        """Test critical error notification."""
        details = {"stack_trace": "line 1\nline 2"}
        notification = notification_service.notify_critical_error(
            db=db,
            error_type="Database Error",
            error_message="Connection pool exhausted",
            details=details,
        )
        db.commit()

        assert notification.type == NotificationType.CRITICAL_ERROR
        assert notification.priority == Priority.CRITICAL
        assert notification.notification_metadata == details

    def test_notify_store_issue(self, db, notification_service):
        """Test store issue notification."""
        notification = notification_service.notify_store_issue(
            db=db,
            store_name="Bad Store",
            issue_type="payment",
            message="Payment method expired",
            store_id=10,
        )
        db.commit()

        assert notification.type == NotificationType.STORE_ISSUE
        assert "Bad Store" in notification.title
        assert notification.priority == Priority.HIGH

    def test_notify_security_alert(self, db, notification_service):
        """Test security alert notification."""
        notification = notification_service.notify_security_alert(
            db=db,
            title="Suspicious Login Attempt",
            message="Multiple failed logins detected",
            details={"ip_address": "1.2.3.4"},
        )
        db.commit()

        assert notification.type == NotificationType.SECURITY_ALERT
        assert notification.priority == Priority.CRITICAL
        assert notification.action_url == "/admin/audit"


@pytest.mark.unit
class TestAdminNotificationServiceQuery:
    """Tests for notification query methods."""

    def test_get_notifications_empty(self, db, notification_service):
        """Test getting notifications when none exist."""
        notifications, total, unread = notification_service.get_notifications(db)

        assert notifications == []
        assert total == 0
        assert unread == 0

    def test_get_notifications_with_data(self, db, notification_service):
        """Test getting notifications with data."""
        # Create some notifications
        notification_service.create_notification(
            db=db,
            notification_type=NotificationType.SYSTEM_ALERT,
            title="Alert 1",
            message="Message 1",
        )
        notification_service.create_notification(
            db=db,
            notification_type=NotificationType.IMPORT_FAILURE,
            title="Alert 2",
            message="Message 2",
            priority=Priority.HIGH,
        )
        db.commit()

        notifications, total, unread = notification_service.get_notifications(db)

        assert total == 2
        assert unread == 2
        assert len(notifications) == 2

    def test_get_notifications_filter_priority(self, db, notification_service):
        """Test filtering by priority."""
        notification_service.create_notification(
            db=db,
            notification_type=NotificationType.SYSTEM_ALERT,
            title="Normal",
            message="Normal priority",
            priority=Priority.NORMAL,
        )
        notification_service.create_notification(
            db=db,
            notification_type=NotificationType.CRITICAL_ERROR,
            title="Critical",
            message="Critical priority",
            priority=Priority.CRITICAL,
        )
        db.commit()

        notifications, total, _ = notification_service.get_notifications(
            db, priority=Priority.CRITICAL
        )

        assert total == 1
        assert notifications[0].title == "Critical"

    def test_get_notifications_filter_read(self, db, notification_service, test_admin):
        """Test filtering by read status."""
        notification_service.create_notification(
            db=db,
            notification_type=NotificationType.SYSTEM_ALERT,
            title="Unread",
            message="Not read yet",
        )
        n2 = notification_service.create_notification(
            db=db,
            notification_type=NotificationType.SYSTEM_ALERT,
            title="Read",
            message="Already read",
        )
        db.commit()

        # Mark one as read
        notification_service.mark_as_read(db, n2.id, test_admin.id)
        db.commit()

        # Get unread only
        notifications, total, _ = notification_service.get_notifications(
            db, is_read=False
        )
        assert total == 1
        assert notifications[0].title == "Unread"

        # Get read only
        notifications, total, _ = notification_service.get_notifications(
            db, is_read=True
        )
        assert total == 1
        assert notifications[0].title == "Read"

    def test_get_notifications_pagination(self, db, notification_service):
        """Test pagination of notifications."""
        for i in range(10):
            notification_service.create_notification(
                db=db,
                notification_type=NotificationType.SYSTEM_ALERT,
                title=f"Alert {i}",
                message=f"Message {i}",
            )
        db.commit()

        notifications, total, _ = notification_service.get_notifications(
            db, skip=0, limit=3
        )
        assert len(notifications) == 3
        assert total == 10

        notifications, total, _ = notification_service.get_notifications(
            db, skip=8, limit=3
        )
        assert len(notifications) == 2  # Only 2 remaining

    def test_get_unread_count(self, db, notification_service):
        """Test unread count."""
        notification_service.create_notification(
            db=db,
            notification_type=NotificationType.SYSTEM_ALERT,
            title="Alert 1",
            message="Message 1",
        )
        notification_service.create_notification(
            db=db,
            notification_type=NotificationType.SYSTEM_ALERT,
            title="Alert 2",
            message="Message 2",
        )
        db.commit()

        assert notification_service.get_unread_count(db) == 2

    def test_get_recent_notifications(self, db, notification_service):
        """Test getting recent unread notifications."""
        for i in range(10):
            notification_service.create_notification(
                db=db,
                notification_type=NotificationType.SYSTEM_ALERT,
                title=f"Alert {i}",
                message=f"Message {i}",
            )
        db.commit()

        recent = notification_service.get_recent_notifications(db, limit=5)
        assert len(recent) == 5


@pytest.mark.unit
class TestAdminNotificationServiceActions:
    """Tests for notification action methods."""

    def test_mark_as_read(self, db, notification_service, test_admin):
        """Test marking notification as read."""
        notification = notification_service.create_notification(
            db=db,
            notification_type=NotificationType.SYSTEM_ALERT,
            title="Test",
            message="Test message",
        )
        db.commit()

        assert notification.is_read is False

        result = notification_service.mark_as_read(db, notification.id, test_admin.id)
        db.commit()

        assert result.is_read is True
        assert result.read_at is not None
        assert result.read_by_user_id == test_admin.id

    def test_mark_as_read_already_read(self, db, notification_service, test_admin):
        """Test marking already-read notification."""
        notification = notification_service.create_notification(
            db=db,
            notification_type=NotificationType.SYSTEM_ALERT,
            title="Test",
            message="Test message",
        )
        db.commit()

        notification_service.mark_as_read(db, notification.id, test_admin.id)
        db.commit()

        original_read_at = notification.read_at

        # Try to mark as read again
        result = notification_service.mark_as_read(db, notification.id, test_admin.id)
        db.commit()

        # Should not update timestamp
        assert result.read_at == original_read_at

    def test_mark_as_read_not_found(self, db, notification_service, test_admin):
        """Test marking nonexistent notification."""
        result = notification_service.mark_as_read(db, 99999, test_admin.id)
        assert result is None

    def test_mark_all_as_read(self, db, notification_service, test_admin):
        """Test marking all notifications as read."""
        for i in range(5):
            notification_service.create_notification(
                db=db,
                notification_type=NotificationType.SYSTEM_ALERT,
                title=f"Alert {i}",
                message=f"Message {i}",
            )
        db.commit()

        assert notification_service.get_unread_count(db) == 5

        count = notification_service.mark_all_as_read(db, test_admin.id)
        db.commit()

        assert count == 5
        assert notification_service.get_unread_count(db) == 0

    def test_delete_notification(self, db, notification_service):
        """Test deleting notification."""
        notification = notification_service.create_notification(
            db=db,
            notification_type=NotificationType.SYSTEM_ALERT,
            title="Test",
            message="Test message",
        )
        db.commit()

        notification_id = notification.id
        result = notification_service.delete_notification(db, notification_id)
        db.commit()

        assert result is True

        # Verify deleted
        notifications, total, _ = notification_service.get_notifications(db)
        assert total == 0

    def test_delete_notification_not_found(self, db, notification_service):
        """Test deleting nonexistent notification."""
        result = notification_service.delete_notification(db, 99999)
        assert result is False

    def test_delete_old_notifications(self, db, notification_service, test_admin):
        """Test deleting old read notifications."""
        # Create old read notification
        old_notification = notification_service.create_notification(
            db=db,
            notification_type=NotificationType.SYSTEM_ALERT,
            title="Old",
            message="Old message",
        )
        db.commit()

        notification_service.mark_as_read(db, old_notification.id, test_admin.id)
        db.commit()

        # Manually backdate it
        old_notification.created_at = datetime.utcnow() - timedelta(days=45)
        db.commit()

        # Create recent notification
        notification_service.create_notification(
            db=db,
            notification_type=NotificationType.SYSTEM_ALERT,
            title="Recent",
            message="Recent message",
        )
        db.commit()

        # Delete old notifications
        count = notification_service.delete_old_notifications(db, days=30)
        db.commit()

        assert count == 1
        notifications, total, _ = notification_service.get_notifications(db)
        assert total == 1
        assert notifications[0].title == "Recent"


# ============================================================================
# PLATFORM ALERT SERVICE TESTS
# ============================================================================


@pytest.mark.unit
class TestPlatformAlertServiceCreate:
    """Tests for platform alert creation."""

    def test_create_alert_basic(self, db, alert_service):
        """Test creating a basic platform alert."""
        alert = alert_service.create_alert(
            db=db,
            alert_type=AlertType.SYSTEM,
            severity=Severity.WARNING,
            title="Test Alert",
        )
        db.commit()

        assert alert.id is not None
        assert alert.alert_type == AlertType.SYSTEM
        assert alert.severity == Severity.WARNING
        assert alert.title == "Test Alert"
        assert alert.is_resolved is False
        assert alert.occurrence_count == 1

    def test_create_alert_with_details(self, db, alert_service):
        """Test creating alert with full details."""
        alert = alert_service.create_alert(
            db=db,
            alert_type=AlertType.DATABASE,
            severity=Severity.CRITICAL,
            title="Database Connection Issue",
            description="Connection pool exhausted",
            affected_stores=[1, 2, 3],
            affected_systems=["api", "worker"],
            auto_generated=True,
        )
        db.commit()

        assert alert.description == "Connection pool exhausted"
        assert alert.affected_stores == [1, 2, 3]
        assert alert.affected_systems == ["api", "worker"]
        assert alert.auto_generated is True


@pytest.mark.unit
class TestPlatformAlertServiceQuery:
    """Tests for platform alert query methods."""

    def test_get_alerts_empty(self, db, alert_service):
        """Test getting alerts when none exist."""
        alerts, total, active, critical = alert_service.get_alerts(db)

        assert alerts == []
        assert total == 0
        assert active == 0
        assert critical == 0

    def test_get_alerts_with_data(self, db, alert_service):
        """Test getting alerts with data."""
        alert_service.create_alert(
            db=db,
            alert_type=AlertType.SYSTEM,
            severity=Severity.WARNING,
            title="Warning Alert",
        )
        alert_service.create_alert(
            db=db,
            alert_type=AlertType.SECURITY,
            severity=Severity.CRITICAL,
            title="Critical Alert",
        )
        db.commit()

        alerts, total, active, critical = alert_service.get_alerts(db)

        assert total == 2
        assert active == 2
        assert critical == 1

    def test_get_alerts_filter_severity(self, db, alert_service):
        """Test filtering by severity."""
        alert_service.create_alert(
            db=db,
            alert_type=AlertType.SYSTEM,
            severity=Severity.WARNING,
            title="Warning",
        )
        alert_service.create_alert(
            db=db,
            alert_type=AlertType.SYSTEM,
            severity=Severity.CRITICAL,
            title="Critical",
        )
        db.commit()

        alerts, total, _, _ = alert_service.get_alerts(db, severity=Severity.CRITICAL)
        assert total == 1
        assert alerts[0].title == "Critical"

    def test_get_alerts_filter_type(self, db, alert_service):
        """Test filtering by alert type."""
        alert_service.create_alert(
            db=db,
            alert_type=AlertType.SECURITY,
            severity=Severity.WARNING,
            title="Security",
        )
        alert_service.create_alert(
            db=db,
            alert_type=AlertType.DATABASE,
            severity=Severity.WARNING,
            title="Database",
        )
        db.commit()

        alerts, total, _, _ = alert_service.get_alerts(db, alert_type=AlertType.SECURITY)
        assert total == 1
        assert alerts[0].title == "Security"

    def test_get_statistics(self, db, alert_service, test_admin):
        """Test getting alert statistics."""
        # Create some alerts
        alert_service.create_alert(
            db=db,
            alert_type=AlertType.SYSTEM,
            severity=Severity.WARNING,
            title="Active Warning",
        )
        alert_service.create_alert(
            db=db,
            alert_type=AlertType.SECURITY,
            severity=Severity.CRITICAL,
            title="Active Critical",
        )
        alert3 = alert_service.create_alert(
            db=db,
            alert_type=AlertType.DATABASE,
            severity=Severity.INFO,
            title="Resolved",
        )
        db.commit()

        # Resolve one today
        alert_service.resolve_alert(db, alert3.id, test_admin.id)
        db.commit()

        stats = alert_service.get_statistics(db)

        assert stats["total_alerts"] == 3
        assert stats["active_alerts"] == 2
        assert stats["critical_alerts"] == 1
        assert stats["resolved_today"] == 1


@pytest.mark.unit
class TestPlatformAlertServiceActions:
    """Tests for platform alert action methods."""

    def test_resolve_alert(self, db, alert_service, test_admin):
        """Test resolving an alert."""
        alert = alert_service.create_alert(
            db=db,
            alert_type=AlertType.SYSTEM,
            severity=Severity.WARNING,
            title="Test Alert",
        )
        db.commit()

        result = alert_service.resolve_alert(
            db,
            alert.id,
            test_admin.id,
            resolution_notes="Fixed by restarting service",
        )
        db.commit()

        assert result.is_resolved is True
        assert result.resolved_at is not None
        assert result.resolved_by_user_id == test_admin.id
        assert result.resolution_notes == "Fixed by restarting service"

    def test_resolve_alert_already_resolved(self, db, alert_service, test_admin):
        """Test resolving already-resolved alert."""
        alert = alert_service.create_alert(
            db=db,
            alert_type=AlertType.SYSTEM,
            severity=Severity.WARNING,
            title="Test Alert",
        )
        db.commit()

        alert_service.resolve_alert(db, alert.id, test_admin.id)
        db.commit()

        original_resolved_at = alert.resolved_at

        # Try resolving again
        result = alert_service.resolve_alert(db, alert.id, test_admin.id)

        # Should not update
        assert result.resolved_at == original_resolved_at

    def test_resolve_alert_not_found(self, db, alert_service, test_admin):
        """Test resolving nonexistent alert."""
        result = alert_service.resolve_alert(db, 99999, test_admin.id)
        assert result is None

    def test_increment_occurrence(self, db, alert_service):
        """Test incrementing alert occurrence count."""
        alert = alert_service.create_alert(
            db=db,
            alert_type=AlertType.SYSTEM,
            severity=Severity.WARNING,
            title="Recurring Alert",
        )
        db.commit()

        original_occurred_at = alert.last_occurred_at
        assert alert.occurrence_count == 1

        result = alert_service.increment_occurrence(db, alert.id)
        db.commit()

        assert result.occurrence_count == 2
        assert result.last_occurred_at > original_occurred_at

    def test_find_similar_active_alert(self, db, alert_service, test_admin):
        """Test finding similar active alert."""
        alert = alert_service.create_alert(
            db=db,
            alert_type=AlertType.SYSTEM,
            severity=Severity.WARNING,
            title="Duplicate Alert",
        )
        db.commit()

        # Should find the active alert
        found = alert_service.find_similar_active_alert(
            db, AlertType.SYSTEM, "Duplicate Alert"
        )
        assert found is not None
        assert found.id == alert.id

        # Resolve the alert
        alert_service.resolve_alert(db, alert.id, test_admin.id)
        db.commit()

        # Should not find resolved alert
        found = alert_service.find_similar_active_alert(
            db, AlertType.SYSTEM, "Duplicate Alert"
        )
        assert found is None

    def test_create_or_increment_alert_new(self, db, alert_service):
        """Test creating new alert when none exists."""
        alert = alert_service.create_or_increment_alert(
            db=db,
            alert_type=AlertType.PERFORMANCE,
            severity=Severity.WARNING,
            title="Performance Issue",
            description="High CPU usage",
        )
        db.commit()

        assert alert.occurrence_count == 1
        assert alert.title == "Performance Issue"

    def test_create_or_increment_alert_existing(self, db, alert_service):
        """Test incrementing existing alert."""
        # Create initial alert
        alert = alert_service.create_or_increment_alert(
            db=db,
            alert_type=AlertType.PERFORMANCE,
            severity=Severity.WARNING,
            title="Performance Issue",
        )
        db.commit()
        alert_id = alert.id

        # Call again with same type/title
        result = alert_service.create_or_increment_alert(
            db=db,
            alert_type=AlertType.PERFORMANCE,
            severity=Severity.WARNING,
            title="Performance Issue",
        )
        db.commit()

        # Should be same alert with incremented count
        assert result.id == alert_id
        assert result.occurrence_count == 2
