# #region Test.Maintenance.Orchestrators [C:4] [TYPE Module] [SEMANTICS test, maintenance, orchestration, coverage, edge]
# @BRIEF Tests for maintenance orchestrators — start_maintenance, end_maintenance, end_all_maintenance.
# @RELATION BINDS_TO -> [MaintenanceOrchestrators]

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

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

import pytest

from src.models.maintenance import (
    MaintenanceDashboardState,
    MaintenanceEvent,
    MaintenanceEventStatus,
    MaintenanceSettings,
)


# ── start_maintenance coverage ──

class TestStartMaintenance:
    """start_maintenance — event start orchestration."""

    @pytest.mark.asyncio
    async def test_dashboard_discovery_failure(self):
        """find_affected_dashboards raises → FAILED status."""
        from src.services.maintenance._orchestrators import start_maintenance

        db = MagicMock()
        event = MaintenanceEvent(
            id="evt1", status=MaintenanceEventStatus.PENDING,
            tables=["table_a"], environment_id="env1",
        )
        # Chain for MaintenanceEvent query
        db.query.return_value.filter.return_value.with_for_update.return_value.first.return_value = event
        # Chain for MaintenanceSettings query
        db.query.return_value.filter.return_value.first.return_value = MaintenanceSettings(
            id="default", target_environment_id="env1", banner_template="Tpl", display_timezone="UTC"
        )

        superset = AsyncMock()

        with patch("src.services.maintenance._orchestrators.find_affected_dashboards",
                   new_callable=AsyncMock, side_effect=Exception("Discovery failed")):
            result = await start_maintenance("evt1", db, superset)
        assert result["status"] == "failed"
        assert "Discovery failed" in result["error"]

    @pytest.mark.asyncio
    async def test_partial_success_some_failures(self):
        """Some dashboards fail → PARTIAL status."""
        from src.services.maintenance._orchestrators import start_maintenance

        db = MagicMock()
        event = MaintenanceEvent(
            id="evt1", status=MaintenanceEventStatus.PENDING,
            tables=["table_a"], environment_id="env1",
        )
        db.query.return_value.filter.return_value.with_for_update.return_value.first.return_value = event
        db.query.return_value.filter.return_value.first.return_value = MaintenanceSettings(
            id="default", target_environment_id="env1", banner_template="Tpl", display_timezone="UTC"
        )

        superset = AsyncMock()

        with patch("src.services.maintenance._orchestrators.find_affected_dashboards",
                   new_callable=AsyncMock, return_value=["dash1", "dash2"]), \
             patch("src.services.maintenance._orchestrators._process_dashboards_for_start",
                   new_callable=AsyncMock, return_value=(
                       ["dash1"],  # successful
                       ["dash2"],  # failed
                       [],  # unmatched
                   )):
            result = await start_maintenance("evt1", db, superset)
        assert result["status"] == "partial"
        assert result["affected_dashboards"] == 1
        assert len(result["failed_dashboards"]) == 1

    @pytest.mark.asyncio
    async def test_all_failures(self):
        """All dashboards fail → FAILED."""
        from src.services.maintenance._orchestrators import start_maintenance

        db = MagicMock()
        event = MaintenanceEvent(
            id="evt1", status=MaintenanceEventStatus.PENDING,
            tables=["table_a"], environment_id="env1",
        )
        db.query.return_value.filter.return_value.with_for_update.return_value.first.return_value = event
        db.query.return_value.filter.return_value.first.return_value = MaintenanceSettings(
            id="default", target_environment_id="env1", banner_template="Tpl", display_timezone="UTC"
        )

        superset = AsyncMock()

        with patch("src.services.maintenance._orchestrators.find_affected_dashboards",
                   new_callable=AsyncMock, return_value=["dash1"]), \
             patch("src.services.maintenance._orchestrators._process_dashboards_for_start",
                   new_callable=AsyncMock, return_value=(
                       [],  # successful
                       ["dash1"],  # failed
                       [],  # unmatched
                   )):
            result = await start_maintenance("evt1", db, superset)
        assert result["status"] == "failed"
        assert result["affected_dashboards"] == 0


# ── end_maintenance ──

class TestEndMaintenance:
    """end_maintenance — event end orchestration."""

    @pytest.mark.asyncio
    async def test_event_not_found(self):
        """Event missing → failed."""
        from src.services.maintenance._orchestrators import end_maintenance
        db = MagicMock()
        db.query.return_value.filter.return_value.with_for_update.return_value.first.return_value = None
        result = await end_maintenance("nonexistent", db, AsyncMock())
        assert result["status"] == "failed"

    @pytest.mark.asyncio
    async def test_already_completed(self):
        """Already completed → idempotent status."""
        from src.services.maintenance._orchestrators import end_maintenance
        db = MagicMock()
        event = MaintenanceEvent(id="evt1", status=MaintenanceEventStatus.COMPLETED)
        db.query.return_value.filter.return_value.with_for_update.return_value.first.return_value = event
        result = await end_maintenance("evt1", db, AsyncMock())
        assert result["status"] == "already_completed"

    @pytest.mark.asyncio
    async def test_success(self):
        """Successful end → completed status."""
        from src.services.maintenance._orchestrators import end_maintenance
        db = MagicMock()
        event = MaintenanceEvent(id="evt1", status=MaintenanceEventStatus.ACTIVE)
        db.query.return_value.filter.return_value.with_for_update.return_value.first.return_value = event
        db.query.return_value.filter.return_value.all.return_value = []

        with patch("src.services.maintenance._orchestrators._process_states_for_end",
                   new_callable=AsyncMock, return_value=(0, [])):
            result = await end_maintenance("evt1", db, AsyncMock())
        assert result["status"] == "completed"

    @pytest.mark.asyncio
    async def test_partial_failed_removals(self):
        """Some removals fail → partial status."""
        from src.services.maintenance._orchestrators import end_maintenance
        db = MagicMock()
        event = MaintenanceEvent(id="evt1", status=MaintenanceEventStatus.ACTIVE)
        db.query.return_value.filter.return_value.with_for_update.return_value.first.return_value = event
        db.query.return_value.filter.return_value.all.return_value = []

        with patch("src.services.maintenance._orchestrators._process_states_for_end",
                   new_callable=AsyncMock, return_value=(1, [{"id": 5, "title": "dash5", "error": "removal failed"}])):
            result = await end_maintenance("evt1", db, AsyncMock())
        assert result["status"] == "partial"
        assert len(result["failed_removals"]) == 1


# ── end_all_maintenance ──

class TestEndAllMaintenance:
    """end_all_maintenance — bulk end."""

    @pytest.mark.asyncio
    async def test_no_active_events(self):
        """No active events → immediate completion."""
        from src.services.maintenance._orchestrators import end_all_maintenance
        db = MagicMock()
        db.query.return_value.filter.return_value.in_.return_value.all.return_value = []
        result = await end_all_maintenance(db, AsyncMock())
        assert result["status"] == "completed"
        assert result["closed_events"] == 0

    @pytest.mark.asyncio
    async def test_one_event_ends_cleanly(self):
        """One active event → ended cleanly."""
        from src.services.maintenance._orchestrators import end_all_maintenance
        db = MagicMock()
        event = MaintenanceEvent(id="evt1", status=MaintenanceEventStatus.ACTIVE)
        db.query.return_value.filter.return_value.all.return_value = [event]

        with patch("src.services.maintenance._orchestrators.end_maintenance",
                   new_callable=AsyncMock, return_value={"removed_from": 3, "failed_removals": []}):
            result = await end_all_maintenance(db, AsyncMock())
        assert result["status"] == "completed"
        assert result["closed_events"] == 1
        assert result["removed_from"] == 3

    @pytest.mark.asyncio
    async def test_end_maintenance_raises_exception(self):
        """end_maintenance raises → event skipped with failed_removals entry."""
        from src.services.maintenance._orchestrators import end_all_maintenance
        db = MagicMock()
        event = MaintenanceEvent(id="evt_fail", status=MaintenanceEventStatus.ACTIVE)
        db.query.return_value.filter.return_value.all.return_value = [event]

        with patch("src.services.maintenance._orchestrators.end_maintenance",
                   new_callable=AsyncMock, side_effect=Exception("end failure")):
            result = await end_all_maintenance(db, AsyncMock())
        assert result["status"] == "partial"
        assert result["closed_events"] == 0
        assert len(result["failed_removals"]) == 1
        assert result["failed_removals"][0]["title"] == "evt_fail"
        assert "end failure" in result["failed_removals"][0]["error"]

    @pytest.mark.asyncio
    async def test_mixed_success_failure(self):
        """Multiple events: some succeed, some fail."""
        from src.services.maintenance._orchestrators import end_all_maintenance
        db = MagicMock()
        evt_ok = MaintenanceEvent(id="evt_ok", status=MaintenanceEventStatus.ACTIVE)
        evt_fail = MaintenanceEvent(id="evt_fail", status=MaintenanceEventStatus.PARTIAL)
        db.query.return_value.filter.return_value.all.return_value = [evt_ok, evt_fail]

        call_count = [0]
        async def mock_end_maintenance(event_id, db_session, superset):
            call_count[0] += 1
            if call_count[0] == 1:
                return {"removed_from": 2, "failed_removals": []}
            raise Exception("second failed")

        with patch("src.services.maintenance._orchestrators.end_maintenance",
                   new_callable=AsyncMock, side_effect=mock_end_maintenance):
            result = await end_all_maintenance(db, AsyncMock())
        assert result["status"] == "partial"
        assert result["closed_events"] == 1
        assert result["removed_from"] == 2
        assert len(result["failed_removals"]) == 1
# #endregion Test.Maintenance.Orchestrators
