"""Extra coverage for notifications/service.py — _send_with_retry, URL validation, service detection."""

from unittest.mock import MagicMock, patch

import pytest

from local_deep_research.notifications.service import (
    NotificationService,
    SendError,
    ServiceError,
)

MODULE = "local_deep_research.notifications.service"


def _make_service():
    """Create a NotificationService with mocked apprise."""
    with patch(f"{MODULE}.apprise") as mock_apprise_module:
        mock_apprise_module.Apprise.return_value = MagicMock()
        svc = NotificationService.__new__(NotificationService)
        svc.apprise = mock_apprise_module.Apprise.return_value
        svc.allow_private_ips = False
        # Tests in this file exercise inner logic; open the gate so
        # send() doesn't bail at the operator-level check.
        svc.outbound_allowed = True
        svc.SERVICE_PATTERNS = NotificationService.SERVICE_PATTERNS
    return svc


# ===========================================================================
# _send_with_retry — direct invocation
# ===========================================================================


class TestSendWithRetry:
    def test_success_returns_true(self):
        svc = _make_service()
        mock_apprise = MagicMock()
        mock_apprise.notify.return_value = True

        # Call _send_with_retry directly (bypasses Tenacity decorator in test)
        result = svc._send_with_retry("Title", "Body", mock_apprise)
        assert result is True
        mock_apprise.notify.assert_called_once()

    def test_failure_raises_send_error(self):
        svc = _make_service()
        mock_apprise = MagicMock()
        mock_apprise.notify.return_value = False

        with pytest.raises(SendError, match="Failed to send"):
            svc._send_with_retry("Title", "Body", mock_apprise)

    def test_with_tag_and_attach(self):
        svc = _make_service()
        mock_apprise = MagicMock()
        mock_apprise.notify.return_value = True

        result = svc._send_with_retry(
            "Title", "Body", mock_apprise, tag="urgent", attach=["/tmp/f.txt"]
        )
        assert result is True
        call_kwargs = mock_apprise.notify.call_args.kwargs
        assert call_kwargs["tag"] == "urgent"
        assert call_kwargs["attach"] == ["/tmp/f.txt"]


# ===========================================================================
# send — URL validation failure
# ===========================================================================


class TestSendUrlValidation:
    def test_invalid_service_url_raises(self):
        svc = _make_service()

        mock_validator = MagicMock()
        mock_validator.validate_multiple_urls.return_value = (
            False,
            "SSRF blocked",
        )

        with (
            patch(f"{MODULE}.NotificationURLValidator", mock_validator),
            pytest.raises((ServiceError, SendError)),
        ):
            svc.send("Title", "Body", service_urls="file:///etc/passwd")

    def test_apprise_add_fails_returns_false(self):
        svc = _make_service()

        mock_validator = MagicMock()
        mock_validator.validate_multiple_urls.return_value = (True, None)

        mock_apprise_mod = MagicMock()
        temp_instance = MagicMock()
        temp_instance.add.return_value = False
        mock_apprise_mod.Apprise.return_value = temp_instance

        with (
            patch(f"{MODULE}.NotificationURLValidator", mock_validator),
            patch(f"{MODULE}.apprise", mock_apprise_mod),
        ):
            result = svc.send("Title", "Body", service_urls="discord://w/t")

        assert result is False

    def test_no_services_configured_returns_false(self):
        svc = _make_service()
        svc.apprise.__len__ = MagicMock(return_value=0)

        result = svc.send("Title", "Body")
        assert result is False


# ===========================================================================
# _validate_url
# ===========================================================================


class TestValidateUrlStatic:
    def test_empty_raises(self):
        with pytest.raises(ServiceError, match="non-empty"):
            NotificationService._validate_url("")

    def test_none_raises(self):
        with pytest.raises(ServiceError, match="non-empty"):
            NotificationService._validate_url(None)

    def test_int_raises(self):
        with pytest.raises(ServiceError, match="non-empty"):
            NotificationService._validate_url(123)

    def test_no_scheme_raises(self):
        with pytest.raises(ServiceError, match="Invalid URL"):
            NotificationService._validate_url("no-scheme-here")

    def test_valid_discord_url(self):
        NotificationService._validate_url("discord://webhook_id/token")

    def test_valid_mailto_url(self):
        NotificationService._validate_url("mailto://user:pass@smtp.com")


# ===========================================================================
# get_service_type
# ===========================================================================


class TestGetServiceType:
    def _get_type(self, url):
        svc = _make_service()
        return svc.get_service_type(url)

    def test_discord(self):
        assert self._get_type("discord://id/token") == "discord"

    def test_slack(self):
        assert self._get_type("slack://token_a/token_b/token_c") == "slack"

    def test_email(self):
        assert self._get_type("mailto://user:pass@smtp.com") == "email"

    def test_telegram(self):
        assert self._get_type("tgram://bot_token/chat_id") == "telegram"

    def test_unknown(self):
        assert self._get_type("custom://something") == "unknown"

    def test_case_insensitive(self):
        # Patterns use re.IGNORECASE
        assert self._get_type("Discord://ID/TOKEN") == "discord"
