"""Tests for notify toggle helpers and cross-worker settings broadcast."""

from unittest.mock import AsyncMock, MagicMock, patch

import pytest

from server.config import settings
from server.infrastructure.settings_broadcast import apply_setting_override, publish_setting_change
from server.infrastructure.telegram import send_telegram_alert, send_telegram_recovery, send_telegram_system_alert
from server.utils.notify_toggles import (
    notify_attr_enabled,
    notify_enabled,
    resource_alert_notify_enabled,
    resource_recovery_notify_enabled,
)


@pytest.mark.parametrize(
    ("value", "expected"),
    [
        ("true", True),
        ("false", False),
        ("0", False),
        ("off", False),
        ("no", False),
        (False, False),
        (True, True),
        (None, True),
    ],
)
def test_notify_enabled(value, expected):
    assert notify_enabled(value) is expected


def test_resource_alert_notify_enabled_respects_cpu_toggle():
    settings.NOTIFY_ALERT_CPU = "false"
    settings.NOTIFY_ALERT_MEM = "true"
    settings.NOTIFY_ALERT_DISK = "true"
    assert resource_alert_notify_enabled("cpu") is False
    assert resource_alert_notify_enabled("mem") is True
    assert resource_alert_notify_enabled("disk") is True


def test_resource_recovery_requires_both_toggles():
    settings.NOTIFY_RECOVERY = "true"
    settings.NOTIFY_ALERT_CPU = "false"
    settings.NOTIFY_ALERT_MEM = "true"
    settings.NOTIFY_ALERT_DISK = "false"
    assert resource_recovery_notify_enabled("cpu") is False
    assert resource_recovery_notify_enabled("mem") is True
    assert resource_recovery_notify_enabled("disk") is False

    settings.NOTIFY_RECOVERY = "false"
    assert resource_recovery_notify_enabled("mem") is False


@pytest.mark.parametrize("metric,attr", [
    ("cpu", "NOTIFY_ALERT_CPU"),
    ("mem", "NOTIFY_ALERT_MEM"),
    ("disk", "NOTIFY_ALERT_DISK"),
])
@pytest.mark.asyncio
async def test_send_telegram_alert_skipped_when_metric_disabled(metric, attr):
    setattr(settings, attr, "false")
    settings.TELEGRAM_BOT_TOKEN = "123:ABC"
    settings.TELEGRAM_CHAT_ID = "1"

    with patch("server.infrastructure.telegram.send_telegram", new_callable=AsyncMock) as mock_send:
        result = await send_telegram_alert("srv-1", metric, 95.0, 1)

    assert result is False
    mock_send.assert_not_awaited()


def test_apply_db_override_notify_toggle():
    settings.NOTIFY_ALERT_CPU = "true"
    assert apply_setting_override("notify_alert_cpu", "false") is True
    assert settings.NOTIFY_ALERT_CPU == "false"


def test_apply_db_override_unknown_key():
    assert apply_setting_override("nonexistent_key", "x") is False


@pytest.mark.asyncio
async def test_publish_setting_change():
    mock_redis = AsyncMock()
    mock_redis.publish = AsyncMock(return_value=1)

    with patch("server.infrastructure.redis.client.get_redis", return_value=mock_redis):
        ok = await publish_setting_change("notify_alert_cpu", "false")

    assert ok is True
    mock_redis.publish.assert_awaited_once()
    channel, payload = mock_redis.publish.await_args.args
    assert channel == "nexus:settings"
    assert '"notify_alert_cpu"' in payload


@pytest.mark.asyncio
async def test_send_telegram_recovery_skipped_when_disabled():
    settings.NOTIFY_RECOVERY = "false"

    with patch("server.infrastructure.telegram.send_telegram", new_callable=AsyncMock) as mock_send:
        result = await send_telegram_recovery("srv-1", "cpu", 50.0, 1)

    assert result is False
    mock_send.assert_not_awaited()


@pytest.mark.parametrize("metric,attr", [
    ("cpu", "NOTIFY_ALERT_CPU"),
    ("mem", "NOTIFY_ALERT_MEM"),
    ("disk", "NOTIFY_ALERT_DISK"),
])
@pytest.mark.asyncio
async def test_send_telegram_recovery_skipped_when_metric_alert_disabled(metric, attr):
    settings.NOTIFY_RECOVERY = "true"
    setattr(settings, attr, "false")

    with patch("server.infrastructure.telegram.send_telegram", new_callable=AsyncMock) as mock_send:
        result = await send_telegram_recovery("srv-1", metric, 50.0, 1)

    assert result is False
    mock_send.assert_not_awaited()


@pytest.mark.asyncio
async def test_send_telegram_recovery_allowed_when_cpu_enabled():
    settings.NOTIFY_RECOVERY = "true"
    settings.NOTIFY_ALERT_CPU = "true"
    settings.TELEGRAM_BOT_TOKEN = "123:ABC"
    settings.TELEGRAM_CHAT_ID = "1"

    with patch("server.infrastructure.telegram.send_telegram", new_callable=AsyncMock) as mock_send:
        mock_send.return_value = True
        result = await send_telegram_recovery("srv-1", "cpu", 50.0, 1)

    assert result is True
    mock_send.assert_awaited_once()


@pytest.mark.parametrize("notify_key", ["NOTIFY_SYSTEM_REDIS", "NOTIFY_SYSTEM_MYSQL"])
@pytest.mark.asyncio
async def test_send_telegram_system_recovery_skipped_when_disabled(notify_key):
    setattr(settings, notify_key, "false")

    with patch("server.infrastructure.telegram.send_telegram", new_callable=AsyncMock) as mock_send:
        result = await send_telegram_system_alert(
            summary="连接已恢复",
            title="服务恢复",
            notify_key=notify_key,
            icon="🟢",
        )

    assert result is False
    mock_send.assert_not_awaited()


@pytest.mark.asyncio
async def test_send_telegram_system_alert_skipped_when_disabled():
    settings.NOTIFY_TIME_DRIFT = "false"

    with patch("server.infrastructure.telegram.send_telegram", new_callable=AsyncMock) as mock_send:
        result = await send_telegram_system_alert(
            summary="test",
            notify_key="NOTIFY_TIME_DRIFT",
        )

    assert result is False
    mock_send.assert_not_awaited()


@pytest.mark.asyncio
async def test_set_setting_publishes_broadcast():
    from server.api.schemas import SettingUpdatePayload
    from server.api.settings import set_setting
    from server.domain.models import Setting

    settings.NOTIFY_ALERT_CPU = "true"
    admin = MagicMock(username="admin")
    request = MagicMock()
    request.client.host = "127.0.0.1"
    db = MagicMock()
    mock_setting = Setting(key="notify_alert_cpu", value="false")

    with patch("server.api.settings.SettingRepositoryImpl") as repo_cls, patch(
        "server.api.settings.AuditLogRepositoryImpl"
    ) as audit_cls, patch(
        "server.infrastructure.settings_broadcast.publish_setting_change",
        new_callable=AsyncMock,
    ) as mock_publish:
        repo = repo_cls.return_value
        repo.set = AsyncMock(return_value=mock_setting)
        audit_cls.return_value.create = AsyncMock()

        await set_setting(
            "notify_alert_cpu",
            SettingUpdatePayload(value="false"),
            request,
            admin,
            db,
        )

    assert settings.NOTIFY_ALERT_CPU == "false"
    mock_publish.assert_awaited_once_with("notify_alert_cpu", "false")


@pytest.mark.parametrize(
    "attr",
    [
        "NOTIFY_ALERT_CPU",
        "NOTIFY_ALERT_MEM",
        "NOTIFY_ALERT_DISK",
        "NOTIFY_RECOVERY",
        "NOTIFY_TIME_DRIFT",
        "NOTIFY_SYSTEM_REDIS",
        "NOTIFY_SYSTEM_MYSQL",
        "NOTIFY_RESTART",
        "NOTIFY_SCRIPT_COMPLETE",
    ],
)
def test_all_notify_attrs_disable(attr):
    setattr(settings, attr, "false")
    assert notify_attr_enabled(attr) is False
