# #region Test.Maintenance.DashboardScanner [C:3] [TYPE Module] [SEMANTICS test, maintenance, dashboard, scanner, filter]
# @BRIEF Tests for dashboard scanner — find_affected_dashboards, _get_linked_dashboards, _apply_dashboard_filters, _resolve_dashboard_title.
# @RELATION BINDS_TO -> [MaintenanceDashboardScanner]

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

import pytest

from src.models.maintenance import DashboardScope, MaintenanceSettings


@pytest.fixture
def mock_superset():
    client = AsyncMock()
    client.get_datasets.return_value = (0, [])
    client.get_dashboards.return_value = (0, [])
    client.get_dataset_detail.return_value = {"linked_dashboards": []}
    return client


class TestFindAffectedDashboards:
    @pytest.mark.asyncio
    async def test_empty_tables(self, mock_superset):
        from src.services.maintenance._dashboard_scanner import find_affected_dashboards
        result = await find_affected_dashboards([], mock_superset)
        assert result == []

    @pytest.mark.asyncio
    async def test_empty_after_normalize(self, mock_superset):
        from src.services.maintenance._dashboard_scanner import find_affected_dashboards
        result = await find_affected_dashboards(["", "  "], mock_superset)
        assert result == []

    @pytest.mark.asyncio
    async def test_superset_fetch_error(self, mock_superset):
        from src.services.maintenance._dashboard_scanner import find_affected_dashboards
        mock_superset.get_datasets.side_effect = Exception("API error")
        with pytest.raises(Exception, match="API error"):
            await find_affected_dashboards(["public.table1"], mock_superset)

    @pytest.mark.asyncio
    async def test_table_based_match(self, mock_superset):
        from src.services.maintenance._dashboard_scanner import find_affected_dashboards
        mock_superset.get_datasets.return_value = (1, [
            {"id": 1, "schema": "public", "table_name": "table1", "sql": None, "is_sqllab_view": False}
        ])
        mock_superset.get_dataset_detail.return_value = {
            "linked_dashboards": [{"id": 10, "title": "Dashboard 10"}]
        }
        result = await find_affected_dashboards(["public.table1"], mock_superset)
        assert 10 in result

    @pytest.mark.asyncio
    async def test_virtual_sql_dataset_match(self, mock_superset):
        from src.services.maintenance._dashboard_scanner import find_affected_dashboards
        mock_superset.get_datasets.return_value = (1, [
            {"id": 2, "schema": "", "table_name": "", "sql": "SELECT * FROM raw.orders", "is_sqllab_view": True}
        ])
        mock_superset.get_dataset_detail.return_value = {
            "linked_dashboards": [{"id": 20, "title": "Dashboard 20"}]
        }
        with patch("src.services.maintenance._dashboard_scanner.extract_tables_from_sql", return_value={"raw.orders"}):
            result = await find_affected_dashboards(["raw.orders"], mock_superset)
            assert 20 in result

    @pytest.mark.asyncio
    async def test_virtual_sql_no_match(self, mock_superset):
        from src.services.maintenance._dashboard_scanner import find_affected_dashboards
        mock_superset.get_datasets.return_value = (1, [
            {"id": 2, "schema": "", "table_name": "", "sql": "SELECT * FROM raw.orders", "is_sqllab_view": True}
        ])
        mock_superset.get_dataset_detail.return_value = {
            "linked_dashboards": [{"id": 20}]
        }
        with patch("src.services.maintenance._dashboard_scanner.extract_tables_from_sql", return_value=set()):
            result = await find_affected_dashboards(["raw.orders"], mock_superset)
            assert result == []

    @pytest.mark.asyncio
    async def test_linked_dashboards_api_failure(self, mock_superset):
        from src.services.maintenance._dashboard_scanner import find_affected_dashboards
        mock_superset.get_datasets.return_value = (1, [
            {"id": 1, "schema": "public", "table_name": "table1", "sql": None, "is_sqllab_view": False}
        ])
        mock_superset.get_dataset_detail.side_effect = Exception("API error")
        result = await find_affected_dashboards(["public.table1"], mock_superset)
        assert result == []  # no linked dashboards due to error

    @pytest.mark.asyncio
    async def test_no_linked_dashboards(self, mock_superset):
        from src.services.maintenance._dashboard_scanner import find_affected_dashboards
        mock_superset.get_datasets.return_value = (1, [
            {"id": 1, "schema": "public", "table_name": "table1", "sql": None, "is_sqllab_view": False}
        ])
        mock_superset.get_dataset_detail.return_value = {"linked_dashboards": []}
        result = await find_affected_dashboards(["public.table1"], mock_superset)
        assert result == []

    @pytest.mark.asyncio
    async def test_with_settings_filtering(self, mock_superset):
        from src.services.maintenance._dashboard_scanner import find_affected_dashboards
        mock_superset.get_datasets.return_value = (1, [
            {"id": 1, "schema": "public", "table_name": "table1", "sql": None, "is_sqllab_view": False}
        ])
        mock_superset.get_dataset_detail.return_value = {
            "linked_dashboards": [{"id": 10, "title": "Dashboard 10"}]
        }
        settings = MaintenanceSettings(
            id="default",
            dashboard_scope=DashboardScope.ALL,
            excluded_dashboard_ids=[],
            forced_dashboard_ids=[],
        )
        result = await find_affected_dashboards(["public.table1"], mock_superset, settings)
        assert 10 in result


class TestGetLinkedDashboards:
    @pytest.mark.asyncio
    async def test_no_dataset_id(self, mock_superset):
        from src.services.maintenance._dashboard_scanner import _get_linked_dashboards
        result = await _get_linked_dashboards(mock_superset, None)
        assert result == set()

    @pytest.mark.asyncio
    async def test_exception_handled(self, mock_superset):
        from src.services.maintenance._dashboard_scanner import _get_linked_dashboards
        mock_superset.get_dataset_detail.side_effect = Exception("API error")
        result = await _get_linked_dashboards(mock_superset, 1)
        assert result == set()

    @pytest.mark.asyncio
    async def test_success(self, mock_superset):
        from src.services.maintenance._dashboard_scanner import _get_linked_dashboards
        mock_superset.get_dataset_detail.return_value = {
            "linked_dashboards": [{"id": 10}, {"id": 20}]
        }
        result = await _get_linked_dashboards(mock_superset, 1)
        assert result == {10, 20}


class TestApplyDashboardFilters:
    @pytest.mark.asyncio
    async def test_no_settings(self, mock_superset):
        from src.services.maintenance._dashboard_scanner import _apply_dashboard_filters
        result = await _apply_dashboard_filters([1, 2], mock_superset, None)
        assert result == [1, 2]

    @pytest.mark.asyncio
    async def test_published_only_filter(self, mock_superset):
        from src.services.maintenance._dashboard_scanner import _apply_dashboard_filters
        mock_superset.get_dashboards.return_value = (2, [
            {"id": 1, "published": True},
            {"id": 2, "published": False},
        ])
        settings = MaintenanceSettings(
            id="default", dashboard_scope=DashboardScope.PUBLISHED_ONLY,
        )
        result = await _apply_dashboard_filters([1, 2], mock_superset, settings)
        assert result == [1]

    @pytest.mark.asyncio
    async def test_draft_only_filter(self, mock_superset):
        from src.services.maintenance._dashboard_scanner import _apply_dashboard_filters
        mock_superset.get_dashboards.return_value = (2, [
            {"id": 1, "published": True},
            {"id": 2, "published": False},
        ])
        settings = MaintenanceSettings(
            id="default", dashboard_scope=DashboardScope.DRAFT_ONLY,
        )
        result = await _apply_dashboard_filters([1, 2], mock_superset, settings)
        assert result == [2]

    @pytest.mark.asyncio
    async def test_excluded_filter(self, mock_superset):
        from src.services.maintenance._dashboard_scanner import _apply_dashboard_filters
        settings = MaintenanceSettings(
            id="default", dashboard_scope=DashboardScope.ALL,
            excluded_dashboard_ids=[2],
        )
        result = await _apply_dashboard_filters([1, 2], mock_superset, settings)
        assert result == [1]

    @pytest.mark.asyncio
    async def test_forced_overrides_excluded(self, mock_superset):
        from src.services.maintenance._dashboard_scanner import _apply_dashboard_filters
        settings = MaintenanceSettings(
            id="default", dashboard_scope=DashboardScope.ALL,
            excluded_dashboard_ids=[2], forced_dashboard_ids=[2],
        )
        result = await _apply_dashboard_filters([1, 2], mock_superset, settings)
        assert 2 in result  # forced overrides excluded

    @pytest.mark.asyncio
    async def test_forced_adds_missing(self, mock_superset):
        from src.services.maintenance._dashboard_scanner import _apply_dashboard_filters
        settings = MaintenanceSettings(
            id="default", dashboard_scope=DashboardScope.ALL,
            forced_dashboard_ids=[99],
        )
        result = await _apply_dashboard_filters([1], mock_superset, settings)
        assert 99 in result

    @pytest.mark.asyncio
    async def test_scope_filtering_failure_fallback(self, mock_superset):
        from src.services.maintenance._dashboard_scanner import _apply_dashboard_filters
        mock_superset.get_dashboards.side_effect = Exception("API error")
        settings = MaintenanceSettings(
            id="default", dashboard_scope=DashboardScope.PUBLISHED_ONLY,
        )
        result = await _apply_dashboard_filters([1], mock_superset, settings)
        # Falls back to unfiltered list when API fails
        assert result == [1]

    @pytest.mark.asyncio
    async def test_scope_dashboard_not_in_list(self, mock_superset):
        from src.services.maintenance._dashboard_scanner import _apply_dashboard_filters
        mock_superset.get_dashboards.return_value = (1, [
            {"id": 10, "published": True},
        ])
        settings = MaintenanceSettings(
            id="default", dashboard_scope=DashboardScope.PUBLISHED_ONLY,
        )
        result = await _apply_dashboard_filters([1], mock_superset, settings)
        assert result == []


class TestResolveDashboardTitle:
    @pytest.mark.asyncio
    async def test_found_by_id(self, mock_superset):
        from src.services.maintenance._dashboard_scanner import _resolve_dashboard_title
        mock_superset.get_dashboards.return_value = (2, [
            {"id": 1, "dashboard_title": "My Dashboard"},
            {"id": 2, "title": "Other"},
        ])
        result = await _resolve_dashboard_title(1, mock_superset)
        assert result == "My Dashboard"

    @pytest.mark.asyncio
    async def test_found_by_title_fallback(self, mock_superset):
        from src.services.maintenance._dashboard_scanner import _resolve_dashboard_title
        mock_superset.get_dashboards.return_value = (1, [
            {"id": 2, "title": "Fallback Title"},
        ])
        result = await _resolve_dashboard_title(2, mock_superset)
        assert result == "Fallback Title"

    @pytest.mark.asyncio
    async def test_not_found_returns_id(self, mock_superset):
        from src.services.maintenance._dashboard_scanner import _resolve_dashboard_title
        mock_superset.get_dashboards.return_value = (0, [])
        result = await _resolve_dashboard_title(99, mock_superset)
        assert result == "99"

    @pytest.mark.asyncio
    async def test_api_exception_returns_id(self, mock_superset):
        from src.services.maintenance._dashboard_scanner import _resolve_dashboard_title
        mock_superset.get_dashboards.side_effect = Exception("API error")
        result = await _resolve_dashboard_title(99, mock_superset)
        assert result == "99"
# #endregion Test.Maintenance.DashboardScanner
