# #region Test.MaintenanceBannerPlugin [C:3] [TYPE Module] [SEMANTICS test, maintenance, banner, plugin, coverage]
# @BRIEF Unit tests for MaintenanceBannerPlugin — properties, get_schema, execute operations, and edge cases.
# @RELATION BINDS_TO -> [MaintenanceBannerPlugin]
# @TEST_EDGE: unknown_operation -> Returns failed status
# @TEST_EDGE: start_without_event_id -> Returns failed status
# @TEST_EDGE: end_without_event_id -> Returns failed status
# @TEST_EDGE: environment_not_found -> Returns failed status
# @TEST_EDGE: db_rollback_on_exception -> Rollback called

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

from src.plugins.maintenance_banner import MaintenanceBannerPlugin


# ── Helpers ──


def _make_env(id_val="env-1", name="Test Env"):
    env = MagicMock()
    env.id = id_val
    env.name = name
    return env


def _make_task_context():
    ctx = MagicMock()
    ctx.logger = MagicMock()
    return ctx


class TestMaintenanceBannerPluginProperties:
    """Verify static property values."""

    def test_id(self):
        plugin = MaintenanceBannerPlugin()
        assert plugin.id == "maintenance_banner_apply"

    def test_name(self):
        plugin = MaintenanceBannerPlugin()
        assert plugin.name == "Maintenance Banner"

    def test_description(self):
        plugin = MaintenanceBannerPlugin()
        assert plugin.description == "Applies or removes maintenance banners on Superset dashboards."

    def test_version(self):
        plugin = MaintenanceBannerPlugin()
        assert plugin.version == "1.0.0"

    def test_ui_route(self):
        plugin = MaintenanceBannerPlugin()
        assert plugin.ui_route == ""


class TestMaintenanceBannerPluginGetSchema:
    """Verify get_schema returns correct structure."""

    def test_get_schema(self):
        plugin = MaintenanceBannerPlugin()
        schema = plugin.get_schema()

        assert schema["type"] == "object"
        assert "operation" in schema["properties"]
        assert schema["properties"]["operation"]["enum"] == ["start", "end", "end_all"]
        assert "operation" in schema["required"]
        assert "event_id" in schema["properties"]
        assert "environment_id" in schema["properties"]


class TestMaintenanceBannerPluginExecute:
    """Verify MaintenanceBannerPlugin.execute with different operations."""

    # ── Unknown operation ──

    @pytest.mark.asyncio
    async def test_execute_unknown_operation(self):
        """Unknown operation returns failed status."""
        plugin = MaintenanceBannerPlugin()
        env = _make_env("env-1", "Test")
        mock_db = MagicMock()
        mock_query = MagicMock()
        mock_query.filter.return_value.first.return_value = None  # no MaintenanceSettings
        mock_db.query.return_value = mock_query

        mock_cm = MagicMock()
        mock_cm.get_environments.return_value = [env]

        with patch('src.plugins.maintenance_banner.SessionLocal', return_value=mock_db), \
             patch('src.plugins.maintenance_banner.get_config_manager', return_value=mock_cm):
            result = await plugin.execute({"operation": "bogus", "environment_id": "env-1"})

        assert result["status"] == "failed"
        assert "Unknown operation" in result["error"]

    # ── start without event_id ──

    @pytest.mark.asyncio
    async def test_execute_start_without_event_id(self):
        """start operation without event_id returns failed."""
        plugin = MaintenanceBannerPlugin()
        env = _make_env("env-1", "Test")
        mock_db = MagicMock()

        mock_cm = MagicMock()
        mock_cm.get_environments.return_value = [env]

        with patch('src.plugins.maintenance_banner.SessionLocal', return_value=mock_db), \
             patch('src.plugins.maintenance_banner.get_config_manager', return_value=mock_cm):
            result = await plugin.execute({"operation": "start", "environment_id": "env-1"})

        assert result["status"] == "failed"
        assert "event_id required" in result["error"]

    # ── end without event_id ──

    @pytest.mark.asyncio
    async def test_execute_end_without_event_id(self):
        """end operation without event_id returns failed."""
        plugin = MaintenanceBannerPlugin()
        env = _make_env("env-1", "Test")
        mock_db = MagicMock()

        mock_cm = MagicMock()
        mock_cm.get_environments.return_value = [env]

        with patch('src.plugins.maintenance_banner.SessionLocal', return_value=mock_db), \
             patch('src.plugins.maintenance_banner.get_config_manager', return_value=mock_cm):
            result = await plugin.execute({"operation": "end", "environment_id": "env-1"})

        assert result["status"] == "failed"
        assert "event_id required" in result["error"]

    # ── Environment not found ──

    @pytest.mark.asyncio
    async def test_execute_environment_not_found(self):
        """Non-matching environment_id returns failed."""
        plugin = MaintenanceBannerPlugin()
        env = _make_env("env-1", "Test")
        mock_db = MagicMock()

        mock_cm = MagicMock()
        mock_cm.get_environments.return_value = [env]

        with patch('src.plugins.maintenance_banner.SessionLocal', return_value=mock_db), \
             patch('src.plugins.maintenance_banner.get_config_manager', return_value=mock_cm):
            result = await plugin.execute({
                "operation": "start",
                "event_id": "evt-1",
                "environment_id": "nonexistent",
            })

        assert result["status"] == "failed"
        assert "not found" in result["error"]

    # ── environment_id resolved from MaintenanceSettings ──

    @pytest.mark.asyncio
    async def test_execute_resolve_env_from_settings(self):
        """environment_id resolved from MaintenanceSettings when not in params."""
        plugin = MaintenanceBannerPlugin()
        env = _make_env("env-default", "DefaultEnv")
        mock_db = MagicMock()
        mock_query = MagicMock()
        mock_settings = MagicMock()
        mock_settings.target_environment_id = "env-default"
        mock_query.filter.return_value.first.return_value = mock_settings
        mock_db.query.return_value = mock_query

        mock_cm = MagicMock()
        mock_cm.get_environments.return_value = [env]

        with patch('src.plugins.maintenance_banner.SessionLocal', return_value=mock_db), \
             patch('src.plugins.maintenance_banner.get_config_manager', return_value=mock_cm), \
             patch('src.plugins.maintenance_banner.start_maintenance', AsyncMock(return_value={"status": "ok"})):
            result = await plugin.execute({
                "operation": "start",
                "event_id": "evt-1",
            })

        assert result["status"] == "ok"

    # ── environment_id resolved from first env when no settings ──

    @pytest.mark.asyncio
    async def test_execute_resolve_env_from_first_env(self):
        """environment_id falls back to first env when no settings match."""
        plugin = MaintenanceBannerPlugin()
        env = _make_env("env-first", "FirstEnv")
        mock_db = MagicMock()
        mock_query = MagicMock()
        mock_query.filter.return_value.first.return_value = None  # no MaintenanceSettings
        mock_db.query.return_value = mock_query

        mock_cm = MagicMock()
        mock_cm.get_environments.return_value = [env]

        with patch('src.plugins.maintenance_banner.SessionLocal', return_value=mock_db), \
             patch('src.plugins.maintenance_banner.get_config_manager', return_value=mock_cm), \
             patch('src.plugins.maintenance_banner.start_maintenance', AsyncMock(return_value={"status": "ok"})):
            result = await plugin.execute({
                "operation": "start",
                "event_id": "evt-1",
            })

        assert result["status"] == "ok"

    # ── start operation calls start_maintenance ──

    @pytest.mark.asyncio
    async def test_execute_start_success(self):
        """start operation delegates to start_maintenance."""
        plugin = MaintenanceBannerPlugin()
        env = _make_env("env-1", "Test")
        mock_db = MagicMock()

        mock_cm = MagicMock()
        mock_cm.get_environments.return_value = [env]

        mock_start = AsyncMock(return_value={"status": "ok", "message": "maintenance started"})

        with patch('src.plugins.maintenance_banner.SessionLocal', return_value=mock_db), \
             patch('src.plugins.maintenance_banner.get_config_manager', return_value=mock_cm), \
             patch('src.plugins.maintenance_banner.start_maintenance', mock_start):
            result = await plugin.execute({
                "operation": "start",
                "event_id": "evt-1",
                "environment_id": "env-1",
            })

        assert result["status"] == "ok"
        mock_start.assert_awaited_once()
        mock_db.commit.assert_called_once()

    # ── end operation calls end_maintenance ──

    @pytest.mark.asyncio
    async def test_execute_end_success(self):
        """end operation delegates to end_maintenance."""
        plugin = MaintenanceBannerPlugin()
        env = _make_env("env-1", "Test")
        mock_db = MagicMock()

        mock_cm = MagicMock()
        mock_cm.get_environments.return_value = [env]

        mock_end = AsyncMock(return_value={"status": "ok", "message": "maintenance ended"})

        with patch('src.plugins.maintenance_banner.SessionLocal', return_value=mock_db), \
             patch('src.plugins.maintenance_banner.get_config_manager', return_value=mock_cm), \
             patch('src.plugins.maintenance_banner.end_maintenance', mock_end):
            result = await plugin.execute({
                "operation": "end",
                "event_id": "evt-1",
                "environment_id": "env-1",
            })

        assert result["status"] == "ok"
        mock_end.assert_awaited_once()

    # ── end_all operation calls end_all_maintenance ──

    @pytest.mark.asyncio
    async def test_execute_end_all_success(self):
        """end_all operation delegates to end_all_maintenance."""
        plugin = MaintenanceBannerPlugin()
        env = _make_env("env-1", "Test")
        mock_db = MagicMock()

        mock_cm = MagicMock()
        mock_cm.get_environments.return_value = [env]

        mock_end_all = AsyncMock(return_value={"status": "ok", "message": "all maintenance ended"})

        with patch('src.plugins.maintenance_banner.SessionLocal', return_value=mock_db), \
             patch('src.plugins.maintenance_banner.get_config_manager', return_value=mock_cm), \
             patch('src.plugins.maintenance_banner.end_all_maintenance', mock_end_all):
            result = await plugin.execute({
                "operation": "end_all",
                "environment_id": "env-1",
            })

        assert result["status"] == "ok"
        mock_end_all.assert_awaited_once()
        mock_db.commit.assert_called_once()

    # ── Exception causes rollback ──

    @pytest.mark.asyncio
    async def test_execute_exception_causes_rollback(self):
        """Exception during operation triggers rollback and returns failed."""
        plugin = MaintenanceBannerPlugin()
        env = _make_env("env-1", "Test")
        mock_db = MagicMock()

        mock_cm = MagicMock()
        mock_cm.get_environments.return_value = [env]

        with patch('src.plugins.maintenance_banner.SessionLocal', return_value=mock_db), \
             patch('src.plugins.maintenance_banner.get_config_manager', return_value=mock_cm), \
             patch('src.plugins.maintenance_banner.start_maintenance',
                   AsyncMock(side_effect=RuntimeError("DB error"))):
            result = await plugin.execute({
                "operation": "start",
                "event_id": "evt-1",
                "environment_id": "env-1",
            })

        assert result["status"] == "failed"
        assert "DB error" in result["error"]
        mock_db.rollback.assert_called_once()
        mock_db.close.assert_called_once()
