"""Tests for chat notifications module."""

from __future__ import annotations

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

import httpx
import pytest

from oclawma.notifications.chat import (
    ChatNotificationConfig,
    ChatNotifier,
    NotificationTrigger,
    WebhookTestResult,
    _get_status_color,
    _get_status_emoji,
)
from oclawma.queue.models import Job, JobPriority, JobStatus


class TestNotificationTrigger:
    """Test NotificationTrigger enum."""

    def test_enum_values(self):
        """Test enum values."""
        assert NotificationTrigger.ON_FAIL.value == "on_fail"
        assert NotificationTrigger.ON_COMPLETE.value == "on_complete"
        assert NotificationTrigger.ON_RETRY.value == "on_retry"


class TestChatNotificationConfig:
    """Test ChatNotificationConfig dataclass."""

    def test_default_creation(self):
        """Test creating config with defaults."""
        config = ChatNotificationConfig()

        assert config.discord_webhook is None
        assert config.slack_webhook is None
        assert config.triggers[NotificationTrigger.ON_FAIL.value] is True
        assert config.triggers[NotificationTrigger.ON_COMPLETE.value] is False
        assert config.triggers[NotificationTrigger.ON_RETRY.value] is False
        assert config.rate_limit_interval == 60.0
        assert config.mention_on_critical is True
        assert config.enabled is True
        assert config.timeout == 10.0

    def test_custom_creation(self):
        """Test creating config with custom values."""
        config = ChatNotificationConfig(
            discord_webhook="https://discord.com/webhook",
            slack_webhook="https://slack.com/webhook",
            triggers={
                "on_fail": False,
                "on_complete": True,
                "on_retry": True,
            },
            rate_limit_interval=30.0,
            mention_on_critical=False,
            discord_user_id="123456",
            slack_user_id="U123456",
            enabled=False,
            timeout=5.0,
        )

        assert config.discord_webhook == "https://discord.com/webhook"
        assert config.slack_webhook == "https://slack.com/webhook"
        assert config.triggers["on_fail"] is False
        assert config.triggers["on_complete"] is True
        assert config.triggers["on_retry"] is True
        assert config.rate_limit_interval == 30.0
        assert config.mention_on_critical is False
        assert config.discord_user_id == "123456"
        assert config.slack_user_id == "U123456"
        assert config.enabled is False
        assert config.timeout == 5.0

    def test_to_dict(self):
        """Test converting config to dictionary."""
        config = ChatNotificationConfig(
            discord_webhook="https://discord.com/webhook",
            triggers={"on_fail": True},
        )

        data = config.to_dict()

        assert data["discord_webhook"] == "https://discord.com/webhook"
        assert data["triggers"]["on_fail"] is True
        assert data["enabled"] is True

    def test_from_dict(self):
        """Test creating config from dictionary."""
        data = {
            "discord_webhook": "https://discord.com/webhook",
            "slack_webhook": "https://slack.com/webhook",
            "triggers": {"on_fail": False, "on_complete": True},
            "rate_limit_interval": 45.0,
            "enabled": False,
        }

        config = ChatNotificationConfig.from_dict(data)

        assert config.discord_webhook == "https://discord.com/webhook"
        assert config.slack_webhook == "https://slack.com/webhook"
        assert config.triggers["on_fail"] is False
        assert config.triggers["on_complete"] is True
        assert config.rate_limit_interval == 45.0
        assert config.enabled is False

    def test_should_trigger(self):
        """Test should_trigger method."""
        config = ChatNotificationConfig(triggers={"on_fail": True, "on_complete": False})

        assert config.should_trigger(NotificationTrigger.ON_FAIL) is True
        assert config.should_trigger(NotificationTrigger.ON_COMPLETE) is False
        assert config.should_trigger(NotificationTrigger.ON_RETRY) is False


class TestStatusHelpers:
    """Test status helper functions."""

    def test_get_status_color(self):
        """Test status color mapping."""
        assert _get_status_color(JobStatus.COMPLETED) == 0x00FF00
        assert _get_status_color(JobStatus.FAILED) == 0xFF0000
        assert _get_status_color(JobStatus.PENDING) == 0xFFA500
        assert _get_status_color(JobStatus.RUNNING) == 0x0099FF
        assert _get_status_color(JobStatus.PAUSED) == 0x808080

    def test_get_status_emoji(self):
        """Test status emoji mapping."""
        assert _get_status_emoji(JobStatus.COMPLETED) == "✅"
        assert _get_status_emoji(JobStatus.FAILED) == "❌"
        assert _get_status_emoji(JobStatus.PENDING) == "⏳"
        assert _get_status_emoji(JobStatus.RUNNING) == "🔄"
        assert _get_status_emoji(JobStatus.PAUSED) == "⏸️"


class TestChatNotifierBasics:
    """Test ChatNotifier basic functionality."""

    def test_default_creation(self):
        """Test creating notifier with defaults."""
        notifier = ChatNotifier()

        assert notifier.config.enabled is True
        assert notifier.config.discord_webhook is None

    def test_custom_config(self):
        """Test creating notifier with custom config."""
        config = ChatNotificationConfig(
            discord_webhook="https://discord.com/webhook",
            enabled=False,
        )
        notifier = ChatNotifier(config)

        assert notifier.config.discord_webhook == "https://discord.com/webhook"
        assert notifier.config.enabled is False

    def test_context_manager(self):
        """Test context manager."""
        with ChatNotifier() as notifier:
            assert notifier.config is not None


class TestChatNotifierNotifications:
    """Test ChatNotifier notification methods."""

    @pytest.fixture
    def completed_job(self):
        """Create a completed job for testing."""
        return Job(
            id=1,
            payload={"task": "test"},
            status=JobStatus.COMPLETED,
            job_type="test_type",
            priority=JobPriority.NORMAL,
            updated_at=datetime.utcnow(),
        )

    @pytest.fixture
    def failed_job(self):
        """Create a failed job for testing."""
        return Job(
            id=2,
            payload={"task": "test"},
            status=JobStatus.FAILED,
            job_type="test_type",
            priority=JobPriority.NORMAL,
            error="Something went wrong",
            retry_count=1,
            max_retries=3,
            updated_at=datetime.utcnow(),
        )

    @pytest.fixture
    def critical_job(self):
        """Create a critical priority job for testing."""
        return Job(
            id=3,
            payload={"task": "critical"},
            status=JobStatus.FAILED,
            job_type="critical_type",
            priority=JobPriority.CRITICAL,
            error="Critical failure",
            updated_at=datetime.utcnow(),
        )

    def test_notify_job_completed_disabled(self, completed_job):
        """Test that disabled notifier doesn't send."""
        config = ChatNotificationConfig(enabled=False, triggers={"on_complete": True})
        notifier = ChatNotifier(config)

        result = notifier.notify_job_completed(completed_job)
        assert result is False

    def test_notify_job_completed_trigger_disabled(self, completed_job):
        """Test that disabled trigger doesn't send."""
        config = ChatNotificationConfig(
            discord_webhook="https://discord.com/webhook",
            triggers={"on_complete": False, "on_fail": True},
        )
        notifier = ChatNotifier(config)

        result = notifier.notify_job_completed(completed_job)
        assert result is False

    @patch("httpx.Client.post")
    def test_notify_job_completed_success(self, mock_post, completed_job):
        """Test successful completion notification."""
        mock_response = MagicMock()
        mock_response.status_code = 204
        mock_response.raise_for_status.return_value = None
        mock_post.return_value = mock_response

        config = ChatNotificationConfig(
            discord_webhook="https://discord.com/webhook",
            triggers={"on_complete": True},
        )
        notifier = ChatNotifier(config)

        result = notifier.notify_job_completed(completed_job)
        assert result is True
        mock_post.assert_called_once()

    @patch("httpx.Client.post")
    def test_notify_job_failed_success(self, mock_post, failed_job):
        """Test successful failure notification."""
        mock_response = MagicMock()
        mock_response.status_code = 204
        mock_response.raise_for_status.return_value = None
        mock_post.return_value = mock_response

        config = ChatNotificationConfig(
            discord_webhook="https://discord.com/webhook",
            triggers={"on_fail": True},
        )
        notifier = ChatNotifier(config)

        result = notifier.notify_job_failed(failed_job)
        assert result is True
        mock_post.assert_called()

    @patch("httpx.Client.post")
    def test_notify_with_slack(self, mock_post, completed_job):
        """Test Slack notification."""
        mock_response = MagicMock()
        mock_response.status_code = 200
        mock_response.raise_for_status.return_value = None
        mock_post.return_value = mock_response

        config = ChatNotificationConfig(
            slack_webhook="https://hooks.slack.com/webhook",
            triggers={"on_complete": True},
        )
        notifier = ChatNotifier(config)

        result = notifier.notify_job_completed(completed_job)
        assert result is True
        mock_post.assert_called_once()

    @patch("httpx.Client.post")
    def test_notify_mention_on_critical(self, mock_post, critical_job):
        """Test that critical jobs trigger mentions."""
        mock_response = MagicMock()
        mock_response.status_code = 204
        mock_response.raise_for_status.return_value = None
        mock_post.return_value = mock_response

        config = ChatNotificationConfig(
            discord_webhook="https://discord.com/webhook",
            discord_user_id="123456",
            triggers={"on_fail": True},
            mention_on_critical=True,
        )
        notifier = ChatNotifier(config)

        result = notifier.notify_job_failed(critical_job)
        assert result is True

        # Check that mention was included in payload
        call_args = mock_post.call_args
        payload = call_args.kwargs.get("json") or call_args.args[1]
        assert "content" in payload
        assert "@" in payload["content"]

    def test_notify_no_webhook_configured(self, completed_job):
        """Test that no webhook means no notification."""
        config = ChatNotificationConfig(
            triggers={"on_complete": True},
        )
        notifier = ChatNotifier(config)

        result = notifier.notify_job_completed(completed_job)
        assert result is False


class TestChatNotifierRateLimiting:
    """Test ChatNotifier rate limiting."""

    @pytest.fixture
    def job(self):
        """Create a job for testing."""
        return Job(
            id=1,
            payload={"task": "test"},
            status=JobStatus.FAILED,
            job_type="test_type",
            priority=JobPriority.NORMAL,
            updated_at=datetime.utcnow(),
        )

    @patch("httpx.Client.post")
    def test_rate_limiting_prevents_duplicate(self, mock_post, job):
        """Test that rate limiting prevents duplicate notifications."""
        mock_response = MagicMock()
        mock_response.status_code = 204
        mock_response.raise_for_status.return_value = None
        mock_post.return_value = mock_response

        config = ChatNotificationConfig(
            discord_webhook="https://discord.com/webhook",
            triggers={"on_fail": True},
            rate_limit_interval=60.0,
        )
        notifier = ChatNotifier(config)

        # First notification should succeed
        result1 = notifier.notify_job_failed(job)
        assert result1 is True
        assert mock_post.call_count == 1

        # Second notification should be rate limited
        result2 = notifier.notify_job_failed(job)
        assert result2 is False
        assert mock_post.call_count == 1  # No additional call


class TestChatNotifierRouting:
    """Test ChatNotifier per-job-type routing."""

    @patch("httpx.Client.post")
    def test_per_job_type_routing(self, mock_post):
        """Test that jobs are routed to correct webhooks."""
        mock_response = MagicMock()
        mock_response.status_code = 204
        mock_response.raise_for_status.return_value = None
        mock_post.return_value = mock_response

        config = ChatNotificationConfig(
            discord_webhook="https://discord.com/default",
            triggers={"on_complete": True},
            channel_routing={
                "special_type": {
                    "discord_webhook": "https://discord.com/special",
                }
            },
        )
        notifier = ChatNotifier(config)

        # Default type job should go to default webhook
        default_job = Job(
            id=1,
            payload={},
            status=JobStatus.COMPLETED,
            job_type="default_type",
            priority=JobPriority.NORMAL,
            updated_at=datetime.utcnow(),
        )
        notifier.notify_job_completed(default_job)

        call_url = mock_post.call_args[0][0]
        assert "default" in call_url

        # Special type job should go to special webhook
        special_job = Job(
            id=2,
            payload={},
            status=JobStatus.COMPLETED,
            job_type="special_type",
            priority=JobPriority.NORMAL,
            updated_at=datetime.utcnow(),
        )
        notifier.notify_job_completed(special_job)

        call_url = mock_post.call_args[0][0]
        assert "special" in call_url


class TestWebhookTestResult:
    """Test WebhookTestResult dataclass."""

    def test_creation(self):
        """Test creating test result."""
        result = WebhookTestResult(
            success=True,
            platform="discord",
            status_code=204,
            response="OK",
            latency_ms=100.5,
        )

        assert result.success is True
        assert result.platform == "discord"
        assert result.status_code == 204
        assert result.response == "OK"
        assert result.latency_ms == 100.5


class TestChatNotifierTests:
    """Test ChatNotifier webhook testing."""

    @patch("httpx.Client.post")
    def test_test_discord_webhook_success(self, mock_post):
        """Test successful Discord webhook test."""
        mock_response = MagicMock()
        mock_response.status_code = 204
        mock_response.raise_for_status.return_value = None
        mock_post.return_value = mock_response

        config = ChatNotificationConfig(discord_webhook="https://discord.com/webhook")
        notifier = ChatNotifier(config)

        result = notifier.test_discord_webhook()

        assert result.success is True
        assert result.platform == "discord"
        assert result.status_code == 204
        assert result.latency_ms is not None

    def test_test_discord_webhook_no_url(self):
        """Test Discord webhook test with no URL."""
        config = ChatNotificationConfig()
        notifier = ChatNotifier(config)

        result = notifier.test_discord_webhook()

        assert result.success is False
        assert result.platform == "discord"
        assert "No webhook URL" in result.response

    @patch("httpx.Client.post")
    def test_test_discord_webhook_http_error(self, mock_post):
        """Test Discord webhook test with HTTP error."""
        mock_response = MagicMock()
        mock_response.status_code = 404
        mock_response.raise_for_status.side_effect = httpx.HTTPStatusError(
            "Not Found",
            request=MagicMock(),
            response=mock_response,
        )
        mock_post.return_value = mock_response

        config = ChatNotificationConfig(discord_webhook="https://discord.com/webhook")
        notifier = ChatNotifier(config)

        result = notifier.test_discord_webhook()

        assert result.success is False
        assert result.status_code == 404

    @patch("httpx.Client.post")
    def test_test_slack_webhook_success(self, mock_post):
        """Test successful Slack webhook test."""
        mock_response = MagicMock()
        mock_response.status_code = 200
        mock_response.raise_for_status.return_value = None
        mock_post.return_value = mock_response

        config = ChatNotificationConfig(slack_webhook="https://hooks.slack.com/webhook")
        notifier = ChatNotifier(config)

        result = notifier.test_slack_webhook()

        assert result.success is True
        assert result.platform == "slack"
        assert result.status_code == 200

    @patch("httpx.Client.post")
    def test_test_all_webhooks(self, mock_post):
        """Test testing all configured webhooks."""
        mock_response = MagicMock()
        mock_response.status_code = 204
        mock_response.raise_for_status.return_value = None
        mock_post.return_value = mock_response

        config = ChatNotificationConfig(
            discord_webhook="https://discord.com/webhook",
            slack_webhook="https://hooks.slack.com/webhook",
        )
        notifier = ChatNotifier(config)

        results = notifier.test_all_webhooks()

        assert len(results) == 2
        platforms = [r.platform for r in results]
        assert "discord" in platforms
        assert "slack" in platforms


class TestDiscordEmbedBuilding:
    """Test Discord embed building."""

    @pytest.fixture
    def notifier(self):
        """Create a notifier for testing."""
        config = ChatNotificationConfig(discord_webhook="https://discord.com/webhook")
        return ChatNotifier(config)

    def test_build_embed_basic(self, notifier):
        """Test basic embed building."""
        job = Job(
            id=1,
            payload={"task": "test"},
            status=JobStatus.COMPLETED,
            job_type="test",
            priority=JobPriority.HIGH,
            updated_at=datetime.utcnow(),
        )

        embed = notifier._build_discord_embed(job, "Test Title", "Test Description")

        assert embed["title"] == "Test Title"
        assert embed["description"] == "Test Description"
        assert embed["color"] == 0x00FF00
        assert len(embed["fields"]) >= 3  # Job ID, Type, Priority

    def test_build_embed_with_error(self, notifier):
        """Test embed building with error."""
        job = Job(
            id=1,
            payload={},
            status=JobStatus.FAILED,
            job_type="test",
            priority=JobPriority.NORMAL,
            error="Error message here",
            updated_at=datetime.utcnow(),
        )

        embed = notifier._build_discord_embed(job, "Failed", "Job failed")

        error_field = [f for f in embed["fields"] if f["name"] == "Error"]
        assert len(error_field) == 1
        assert "Error message here" in error_field[0]["value"]

    def test_build_embed_with_retry(self, notifier):
        """Test embed building with retry info."""
        job = Job(
            id=1,
            payload={},
            status=JobStatus.FAILED,
            job_type="test",
            priority=JobPriority.NORMAL,
            retry_count=2,
            max_retries=5,
            updated_at=datetime.utcnow(),
        )

        embed = notifier._build_discord_embed(job, "Retry", "Job retrying")

        retry_field = [f for f in embed["fields"] if f["name"] == "Retry"]
        assert len(retry_field) == 1
        assert "2/5" in retry_field[0]["value"]


class TestSlackBlocksBuilding:
    """Test Slack blocks building."""

    @pytest.fixture
    def notifier(self):
        """Create a notifier for testing."""
        config = ChatNotificationConfig(slack_webhook="https://hooks.slack.com/webhook")
        return ChatNotifier(config)

    def test_build_blocks_basic(self, notifier):
        """Test basic blocks building."""
        job = Job(
            id=1,
            payload={"task": "test"},
            status=JobStatus.COMPLETED,
            job_type="test",
            priority=JobPriority.HIGH,
            updated_at=datetime.utcnow(),
        )

        blocks = notifier._build_slack_blocks(job, "Test Title", "Test Description")

        assert len(blocks) >= 3  # Header, section, context
        assert blocks[0]["type"] == "header"
        assert blocks[1]["type"] == "section"

    def test_build_blocks_with_error(self, notifier):
        """Test blocks building with error."""
        job = Job(
            id=1,
            payload={},
            status=JobStatus.FAILED,
            job_type="test",
            priority=JobPriority.NORMAL,
            error="Error message here",
            updated_at=datetime.utcnow(),
        )

        blocks = notifier._build_slack_blocks(job, "Failed", "Job failed")

        error_block = [b for b in blocks if b["type"] == "section" and "Error" in str(b)]
        assert len(error_block) >= 1


class TestErrorHandling:
    """Test error handling in notifications."""

    @pytest.fixture
    def job(self):
        """Create a job for testing."""
        return Job(
            id=1,
            payload={},
            status=JobStatus.FAILED,
            job_type="test",
            priority=JobPriority.NORMAL,
            updated_at=datetime.utcnow(),
        )

    @patch("httpx.Client.post")
    def test_http_error_handling(self, mock_post, job):
        """Test handling of HTTP errors."""
        mock_post.side_effect = httpx.RequestError("Connection failed")

        config = ChatNotificationConfig(
            discord_webhook="https://discord.com/webhook",
            triggers={"on_fail": True},
        )
        notifier = ChatNotifier(config)

        result = notifier.notify_job_failed(job)
        assert result is False

    @patch("httpx.Client.post")
    def test_timeout_error_handling(self, mock_post, job):
        """Test handling of timeout errors."""
        mock_post.side_effect = httpx.TimeoutException("Request timed out")

        config = ChatNotificationConfig(
            discord_webhook="https://discord.com/webhook",
            triggers={"on_fail": True},
            timeout=1.0,
        )
        notifier = ChatNotifier(config)

        result = notifier.notify_job_failed(job)
        assert result is False

    @patch("httpx.Client.post")
    def test_unexpected_error_handling(self, mock_post, job):
        """Test handling of unexpected errors."""
        mock_post.side_effect = ValueError("Unexpected error")

        config = ChatNotificationConfig(
            discord_webhook="https://discord.com/webhook",
            triggers={"on_fail": True},
        )
        notifier = ChatNotifier(config)

        result = notifier.notify_job_failed(job)
        assert result is False
