# #region Test.NotificationProviders [C:3] [TYPE Module] [SEMANTICS test,notification,provider,smtp,telegram,slack]
# @BRIEF Tests for notifications/providers.py — SMTP, Telegram, Slack providers with mocked network.
# @RELATION BINDS_TO -> [providers]

from pathlib import Path
import sys

sys.path.insert(0, str(Path(__file__).parent.parent / "src"))

from unittest.mock import AsyncMock, MagicMock, patch
import pytest


class TestSMTPProvider:
    """SMTPProvider — delivers notifications via SMTP."""

    @pytest.fixture
    def config(self):
        return {
            "host": "smtp.example.com",
            "port": 587,
            "username": "user",
            "password": "pass",
            "from_email": "noreply@example.com",
            "use_tls": True,
        }

    @pytest.fixture
    def provider(self, config):
        from src.services.notifications.providers import SMTPProvider
        return SMTPProvider(config)

    def test_init(self, provider):
        assert provider.host == "smtp.example.com"
        assert provider.port == 587
        assert provider.from_email == "noreply@example.com"
        assert provider.use_tls is True

    def test_init_defaults(self):
        from src.services.notifications.providers import SMTPProvider
        p = SMTPProvider({"host": "smtp.x.com"})
        assert p.port == 587
        assert p.use_tls is True
        assert p.username is None
        assert p.password is None

    @pytest.mark.asyncio
    async def test_send_success(self, provider):
        mock_smtp = AsyncMock()
        mock_smtp.__aenter__.return_value = mock_smtp

        with patch("src.services.notifications.providers.aiosmtplib.SMTP", return_value=mock_smtp):
            result = await provider.send("admin@x.com", "Test", "Body")
            assert result is True
            mock_smtp.sendmail.assert_called_once()

    @pytest.mark.asyncio
    async def test_send_failure_returns_false(self, provider):
        mock_smtp = AsyncMock()
        mock_smtp.__aenter__.side_effect = ConnectionError("SMTP timeout")

        with patch("src.services.notifications.providers.aiosmtplib.SMTP", return_value=mock_smtp):
            result = await provider.send("admin@x.com", "Test", "Body")
            assert result is False

    @pytest.mark.asyncio
    async def test_send_no_auth(self, config):
        from src.services.notifications.providers import SMTPProvider
        p = SMTPProvider({"host": "smtp.x.com", "username": None, "password": None})
        mock_smtp = AsyncMock()
        mock_smtp.__aenter__.return_value = mock_smtp

        with patch("src.services.notifications.providers.aiosmtplib.SMTP", return_value=mock_smtp):
            result = await p.send("admin@x.com", "Test", "Body")
            assert result is True
            mock_smtp.login.assert_not_called()

    @pytest.mark.asyncio
    async def test_send_no_tls(self, config):
        from src.services.notifications.providers import SMTPProvider
        p = SMTPProvider({"host": "smtp.x.com", "use_tls": False})
        mock_smtp = AsyncMock()
        mock_smtp.__aenter__.return_value = mock_smtp

        with patch("src.services.notifications.providers.aiosmtplib.SMTP", return_value=mock_smtp):
            result = await p.send("admin@x.com", "Test", "Body")
            assert result is True
            mock_smtp.starttls.assert_not_called()


class TestTelegramProvider:
    """TelegramProvider — delivers via Telegram Bot API."""

    @pytest.fixture
    def config(self):
        return {"bot_token": "123:ABC"}

    @pytest.fixture
    def provider(self, config):
        from src.services.notifications.providers import TelegramProvider
        return TelegramProvider(config)

    def test_init(self, provider):
        assert provider.bot_token == "123:ABC"

    @pytest.mark.asyncio
    async def test_send_success(self, provider):
        mock_client = AsyncMock()
        mock_response = MagicMock()
        mock_response.raise_for_status.return_value = None
        mock_client.post.return_value = mock_response

        with patch("src.services.notifications.providers._get_http_client",
                   return_value=mock_client):
            result = await provider.send("@admin", "Alert", "DB issue")
            assert result is True
            mock_client.post.assert_called_once()

    @pytest.mark.asyncio
    async def test_send_no_token_returns_false(self):
        from src.services.notifications.providers import TelegramProvider
        p = TelegramProvider({"bot_token": None})
        result = await p.send("@admin", "Alert", "Body")
        assert result is False

    @pytest.mark.asyncio
    async def test_send_failure_returns_false(self, provider):
        mock_client = AsyncMock()
        mock_client.post.side_effect = ConnectionError("API timeout")

        with patch("src.services.notifications.providers._get_http_client",
                   return_value=mock_client):
            result = await provider.send("@admin", "Alert", "Body")
            assert result is False

    @pytest.mark.asyncio
    async def test_send_http_error_returns_false(self, provider):
        mock_client = AsyncMock()
        mock_response = MagicMock()
        mock_response.raise_for_status.side_effect = ConnectionError("HTTP 500")
        mock_client.post.return_value = mock_response

        with patch("src.services.notifications.providers._get_http_client",
                   return_value=mock_client):
            result = await provider.send("@admin", "Alert", "Body")
            assert result is False


class TestSlackProvider:
    """SlackProvider — delivers via Slack Webhooks."""

    @pytest.fixture
    def config(self):
        return {"webhook_url": "https://hooks.slack.com/services/xxx"}

    @pytest.fixture
    def provider(self, config):
        from src.services.notifications.providers import SlackProvider
        return SlackProvider(config)

    def test_init(self, provider):
        assert provider.webhook_url == "https://hooks.slack.com/services/xxx"

    @pytest.mark.asyncio
    async def test_send_success(self, provider):
        mock_client = AsyncMock()
        mock_response = MagicMock()
        mock_response.raise_for_status.return_value = None
        mock_client.post.return_value = mock_response

        with patch("src.services.notifications.providers._get_http_client",
                   return_value=mock_client):
            result = await provider.send("#alerts", "Alert", "DB issue")
            assert result is True
            mock_client.post.assert_called_once()

    @pytest.mark.asyncio
    async def test_send_no_webhook_returns_false(self):
        from src.services.notifications.providers import SlackProvider
        p = SlackProvider({"webhook_url": None})
        result = await p.send("#alerts", "Alert", "Body")
        assert result is False

    @pytest.mark.asyncio
    async def test_send_failure_returns_false(self, provider):
        mock_client = AsyncMock()
        mock_client.post.side_effect = ConnectionError("Webhook timeout")

        with patch("src.services.notifications.providers._get_http_client",
                   return_value=mock_client):
            result = await provider.send("#alerts", "Alert", "Body")
            assert result is False

    @pytest.mark.asyncio
    async def test_send_http_error_returns_false(self, provider):
        mock_client = AsyncMock()
        mock_response = MagicMock()
        mock_response.raise_for_status.side_effect = ConnectionError("HTTP 500")
        mock_client.post.return_value = mock_response

        with patch("src.services.notifications.providers._get_http_client",
                   return_value=mock_client):
            result = await provider.send("#alerts", "Alert", "Body")
            assert result is False

class TestGetHttpClient:
    """_get_http_client — singleton client creation (lines 41-46)."""

    @pytest.mark.asyncio
    async def test_creates_and_reuses_client(self):
        from src.services.notifications.providers import _get_http_client, _http_client
        # Reset singleton
        import src.services.notifications.providers as providers_mod
        providers_mod._http_client = None

        client1 = await _get_http_client()
        assert client1 is not None
        client2 = await _get_http_client()
        assert client2 is client1  # same instance reused

        # Cleanup
        await client1.aclose()
        providers_mod._http_client = None

    @pytest.mark.asyncio
    async def test_client_has_timeout(self):
        from src.services.notifications.providers import _get_http_client
        import src.services.notifications.providers as providers_mod
        providers_mod._http_client = None

        client = await _get_http_client()
        assert client.timeout is not None
        await client.aclose()
        providers_mod._http_client = None

    @pytest.mark.asyncio
    async def test_abstract_send_has_pass(self):
        """Cover abstract method pass at line 72."""
        from src.services.notifications.providers import NotificationProvider
        # Patch __abstractmethods__ to bypass ABC instantiation check
        import unittest.mock as mock
        with mock.patch.object(NotificationProvider, '__abstractmethods__', set()):
            p = NotificationProvider()
            result = await p.send("target", "subject", "body", {})
            assert result is None  # pass returns None
# #endregion Test.NotificationProviders
