"""Tests for notification queue integration."""

from __future__ import annotations

from datetime import datetime
from unittest.mock import MagicMock, patch

import pytest

from oclawma.notifications.chat import ChatNotificationConfig
from oclawma.notifications.queue_integration import NotificationMixin, NotifyingJobQueue
from oclawma.queue.models import Job, JobPriority, JobStatus


class TestNotifyingJobQueue:
    """Test NotifyingJobQueue integration."""

    @pytest.fixture
    def notification_config(self):
        """Create a notification config for testing."""
        return ChatNotificationConfig(
            discord_webhook="https://discord.com/webhook",
            triggers={
                "on_fail": True,
                "on_complete": True,
                "on_retry": False,
            },
        )

    @pytest.fixture
    def queue(self, tmp_path, notification_config):
        """Create a NotifyingJobQueue for testing."""
        db_path = tmp_path / "test_queue.db"
        queue = NotifyingJobQueue(
            db_path=db_path,
            notification_config=notification_config,
        )
        yield queue
        queue.close()

    def test_queue_creation_with_notifications(self, tmp_path, notification_config):
        """Test creating queue with notifications."""
        db_path = tmp_path / "test.db"
        queue = NotifyingJobQueue(db_path, notification_config=notification_config)

        assert queue.notification_config is not None
        assert queue.notification_config.enabled is True
        assert queue._notifier is not None
        queue.close()

    def test_queue_creation_without_notifications(self, tmp_path):
        """Test creating queue without notifications."""
        db_path = tmp_path / "test.db"
        queue = NotifyingJobQueue(db_path)

        assert queue.notification_config is None
        assert queue._notifier is None
        queue.close()

    def test_queue_creation_disabled_notifications(self, tmp_path, notification_config):
        """Test creating queue with disabled notifications."""
        notification_config.enabled = False
        db_path = tmp_path / "test.db"
        queue = NotifyingJobQueue(db_path, notification_config=notification_config)

        assert queue.notification_config.enabled is False
        assert queue._notifier is None
        queue.close()

    @patch("httpx.Client.post")
    def test_complete_sends_notification(self, mock_post, queue):
        """Test that completing a job sends notification."""
        mock_response = MagicMock()
        mock_response.status_code = 204
        mock_response.raise_for_status.return_value = None
        mock_post.return_value = mock_response

        job = queue.enqueue({"task": "test"})
        queue.dequeue()  # Move to running
        queue.complete(job.id)

        mock_post.assert_called()

    @patch("httpx.Client.post")
    def test_fail_sends_notification(self, mock_post, queue):
        """Test that failing a job sends notification."""
        mock_response = MagicMock()
        mock_response.status_code = 204
        mock_response.raise_for_status.return_value = None
        mock_post.return_value = mock_response

        job = queue.enqueue({"task": "test"})
        queue.dequeue()
        queue.fail(job.id, "Test error")

        mock_post.assert_called()

    def test_retry_does_not_notify_when_disabled(self, queue):
        """Test that retry doesn't notify when trigger is disabled."""
        with patch.object(queue._notifier, "notify_job_retry") as mock_notify:
            job = queue.enqueue({"task": "test"}, max_retries=3)
            queue.dequeue()
            queue.fail(job.id, "Error")
            queue.retry(job.id)

            # Retry trigger is disabled in config
            mock_notify.assert_not_called()

    def test_notification_failure_does_not_break_queue(self, queue):
        """Test that notification failures don't break queue operations."""
        with patch.object(
            queue._notifier, "notify_job_completed", side_effect=Exception("Notification failed")
        ):
            job = queue.enqueue({"task": "test"})
            queue.dequeue()

            # Should not raise
            completed = queue.complete(job.id)
            assert completed.status == JobStatus.COMPLETED

    def test_update_notification_config(self, queue):
        """Test updating notification configuration."""
        new_config = ChatNotificationConfig(
            slack_webhook="https://slack.com/webhook",
            enabled=True,
        )

        queue.update_notification_config(new_config)

        assert queue.notification_config.slack_webhook == "https://slack.com/webhook"
        assert queue._notifier is not None

    def test_update_notification_config_disable(self, queue):
        """Test disabling notifications via update."""
        new_config = ChatNotificationConfig(enabled=False)

        queue.update_notification_config(new_config)

        assert queue.notification_config.enabled is False
        assert queue._notifier is None

    @patch("httpx.Client.post")
    def test_test_notifications(self, mock_post, queue):
        """Test the test_notifications method."""
        mock_response = MagicMock()
        mock_response.status_code = 204
        mock_response.raise_for_status.return_value = None
        mock_post.return_value = mock_response

        results = queue.test_notifications()

        assert len(results) > 0

    def test_test_notifications_no_notifier(self, tmp_path):
        """Test test_notifications with no notifier."""
        db_path = tmp_path / "test.db"
        queue = NotifyingJobQueue(db_path)

        results = queue.test_notifications()
        assert results == []
        queue.close()

    def test_close_cleans_up_notifier(self, tmp_path, notification_config):
        """Test that close cleans up the notifier."""
        db_path = tmp_path / "test.db"
        queue = NotifyingJobQueue(db_path, notification_config=notification_config)

        assert queue._notifier is not None
        queue.close()

        # Notifier should be closed and set to None
        assert queue._notifier is None


class TestNotificationMixin:
    """Test NotificationMixin."""

    def test_mixin_init(self):
        """Test that mixin initializes notifications."""

        class TestClass:
            pass

        class NotifyingClass(NotificationMixin, TestClass):
            def __init__(self):
                config = ChatNotificationConfig(
                    discord_webhook="https://discord.com/webhook",
                    enabled=True,
                )
                self._init_notifications(config)

        obj = NotifyingClass()
        assert obj._notifier is not None
        assert obj.notification_config.enabled is True

    def test_mixin_notify_if_enabled(self):
        """Test mixin notification method."""

        class TestClass:
            pass

        class NotifyingClass(NotificationMixin, TestClass):
            def __init__(self):
                config = ChatNotificationConfig(
                    discord_webhook="https://discord.com/webhook",
                    triggers={"on_complete": True},
                    enabled=True,
                )
                self._init_notifications(config)

        obj = NotifyingClass()

        job = Job(
            id=1,
            payload={},
            status=JobStatus.COMPLETED,
            job_type="test",
            priority=JobPriority.NORMAL,
            updated_at=datetime.utcnow(),
        )

        with patch.object(obj._notifier, "notify_job_completed") as mock_notify:
            obj._notify_if_enabled(job, "on_complete")
            mock_notify.assert_called_once_with(job)

    def test_mixin_notify_respects_triggers(self):
        """Test that mixin respects trigger settings."""

        class TestClass:
            pass

        class NotifyingClass(NotificationMixin, TestClass):
            def __init__(self):
                config = ChatNotificationConfig(
                    discord_webhook="https://discord.com/webhook",
                    triggers={"on_complete": False},
                    enabled=True,
                )
                self._init_notifications(config)

        obj = NotifyingClass()

        job = Job(
            id=1,
            payload={},
            status=JobStatus.COMPLETED,
            job_type="test",
            priority=JobPriority.NORMAL,
            updated_at=datetime.utcnow(),
        )

        with patch.object(obj._notifier, "notify_job_completed") as mock_notify:
            obj._notify_if_enabled(job, "on_complete")
            mock_notify.assert_not_called()


class TestNotifyingJobQueueProcessNext:
    """Test process_next with notifications."""

    @pytest.fixture
    def queue(self, tmp_path):
        """Create a NotifyingJobQueue for testing."""
        config = ChatNotificationConfig(
            discord_webhook="https://discord.com/webhook",
            triggers={"on_complete": True, "on_fail": True},
        )
        db_path = tmp_path / "test_queue.db"
        queue = NotifyingJobQueue(
            db_path=db_path,
            notification_config=config,
        )
        yield queue
        queue.close()

    @patch("httpx.Client.post")
    def test_process_next_success(self, mock_post, queue):
        """Test process_next with successful handler."""
        mock_response = MagicMock()
        mock_response.status_code = 204
        mock_response.raise_for_status.return_value = None
        mock_post.return_value = mock_response

        queue.enqueue({"task": "test"})

        def handler(job):
            pass  # Success

        result = queue.process_next(handler)

        assert result is True
        mock_post.assert_called()  # Completion notification sent

    @patch("httpx.Client.post")
    def test_process_next_failure(self, mock_post, queue):
        """Test process_next with failing handler."""
        mock_response = MagicMock()
        mock_response.status_code = 204
        mock_response.raise_for_status.return_value = None
        mock_post.return_value = mock_response

        job = queue.enqueue({"task": "test"}, max_retries=0)

        def handler(job):
            raise ValueError("Test error")

        with pytest.raises(ValueError):
            queue.process_next(handler)

        # Job should be failed
        job = queue.get_job(job.id)
        assert job.status == JobStatus.FAILED
