# #region MaintenanceDashboardScanner [C:3] [TYPE Module] [SEMANTICS maintenance, dashboard, scanner, tables]
# @BRIEF Dashboard discovery and filtering helpers for maintenance banner feature.
#   Scan Superset datasets, match tables, apply scope/excluded/forced filters.
# @LAYER Service
# @RELATION DEPENDS_ON -> [SupersetClient]
# @RELATION DEPENDS_ON -> [SqlTableExtractorModule]
# @RELATION DEPENDS_ON -> [MaintenanceModels]



from ...core.logger import belief_scope, logger as app_logger
from ...core.superset_client import SupersetClient
from ...models.maintenance import DashboardScope, MaintenanceSettings
from ..sql_table_extractor import extract_tables_from_sql


# #region find_affected_dashboards [C:3] [TYPE Function] [SEMANTICS maintenance, dashboard, discovery, tables]
# @BRIEF Find all dashboard IDs whose datasets reference any of the given tables.
#   Supports both table-based (schema, table_name) and virtual SQL datasets (via SqlTableExtractor).
#   Applies scope/excluded/forced filtering from MaintenanceSettings.
# @PRE tables is a non-empty list of "schema.table" strings. superset_client is authenticated.
# @POST Returns a deduplicated list of dashboard IDs that should receive banners.
# @SIDE_EFFECT Fetches all datasets from Superset (paginated). May be slow with many datasets.
# @RELATION DEPENDS_ON -> [SupersetClient]
# @RELATION DEPENDS_ON -> [SqlTableExtractorModule]
def find_affected_dashboards(
    tables: list[str],
    superset_client: SupersetClient,
    settings: MaintenanceSettings | None = None,
) -> list[int]:
    with belief_scope(
        "find_affected_dashboards",
        f"tables={tables}",
    ):
        app_logger.reason(
            "Finding affected dashboards",
            extra={"tables": tables},
        )

        if not tables:
            app_logger.reflect("No tables provided, returning empty")
            return []

        # Normalize input tables to lowercase set for matching
        target_tables = {t.lower().strip() for t in tables if t.strip()}
        if not target_tables:
            return []

        # Fetch all datasets from Superset
        try:
            _, datasets = superset_client.get_datasets()
        except Exception as e:
            app_logger.explore(
                "Failed to fetch datasets from Superset",
                extra={},
                error=str(e),
            )
            raise

        app_logger.reason(
            f"Scanning {len(datasets)} datasets for table references",
            extra={"target_tables": list(target_tables)},
        )

        matched_dashboard_ids: set[int] = set()

        for ds in datasets:
            ds_schema = (ds.get("schema") or "").lower().strip()
            ds_table = (ds.get("table_name") or "").lower().strip()
            is_virtual = ds.get("is_sqllab_view", False) or ds.get("sql", "") not in (
                None,
                "",
            )
            ds_sql = ds.get("sql") or ""
            ds_id = ds.get("id")

            # Phase 1: Table-based match
            if not is_virtual and ds_schema and ds_table:
                qualified = f"{ds_schema}.{ds_table}"
                if qualified in target_tables:
                    linked = _get_linked_dashboards(superset_client, ds_id)
                    matched_dashboard_ids.update(linked)
                    continue

            # Phase 2: Virtual/SQL dataset — extract tables from SQL text
            if is_virtual and ds_sql:
                extracted = extract_tables_from_sql(ds_sql)
                if extracted & target_tables:
                    linked = _get_linked_dashboards(superset_client, ds_id)
                    matched_dashboard_ids.update(linked)

        app_logger.reflect(
            f"Found {len(matched_dashboard_ids)} affected dashboards",
            extra={"dashboard_ids": list(matched_dashboard_ids)},
        )

        # Apply filtering from settings
        result = _apply_dashboard_filters(
            list(matched_dashboard_ids), superset_client, settings
        )

        app_logger.reflect(
            f"After filtering: {len(result)} dashboards",
            extra={"dashboard_ids": result},
        )
        return result
# #endregion find_affected_dashboards


# #region _get_linked_dashboards [C:2] [TYPE Function]
# @BRIEF Fetch linked dashboards for a dataset from Superset.
# @SIDE_EFFECT Calls Superset API.
def _get_linked_dashboards(
    superset_client: SupersetClient,
    dataset_id: int | None,
) -> set[int]:
    if not dataset_id:
        return set()
    try:
        detail = superset_client.get_dataset_detail(dataset_id)
        linked = detail.get("linked_dashboards", [])
        return {int(d["id"]) for d in linked if d.get("id")}
    except Exception as e:
        app_logger.explore(
            "Failed to fetch linked dashboards",
            extra={"dataset_id": dataset_id},
            error=str(e),
        )
        return set()
# #endregion _get_linked_dashboards


# #region _apply_dashboard_filters [C:2] [TYPE Function]
# @BRIEF Apply scope (published/draft/all), excluded, and forced filters from settings.
def _apply_dashboard_filters(
    dashboard_ids: list[int],
    superset_client: SupersetClient,
    settings: MaintenanceSettings | None,
) -> list[int]:
    if not settings:
        return dashboard_ids

    # If forced list exists, include those unconditionally
    forced_ids = set(settings.forced_dashboard_ids or [])
    excluded_ids = set(settings.excluded_dashboard_ids or [])

    # Apply scope filtering
    if settings.dashboard_scope != DashboardScope.ALL:
        try:
            _, all_dashboards = superset_client.get_dashboards()
            scope = settings.dashboard_scope
            filtered: list[int] = []
            for did in dashboard_ids:
                dash = next(
                    (d for d in all_dashboards if d.get("id") == did), None
                )
                if dash is None:
                    continue
                is_published = dash.get("published", True)
                if scope == DashboardScope.PUBLISHED_ONLY and not is_published:
                    continue
                if scope == DashboardScope.DRAFT_ONLY and is_published:
                    continue
                filtered.append(did)
            dashboard_ids = filtered
        except Exception as e:
            app_logger.explore(
                "Scope filtering failed, proceeding without it",
                extra={},
                error=str(e),
            )

    # Apply excluded (forced overrides excluded per FR-009-b)
    result = [
        did for did in dashboard_ids if did not in excluded_ids or did in forced_ids
    ]

    # Add forced dashboards that are not already included
    for fid in forced_ids:
        if fid not in result:
            result.append(fid)

    return result
# #endregion _apply_dashboard_filters


# #region _resolve_dashboard_title [C:2] [TYPE Function]
# @BRIEF Resolve dashboard title from Superset by ID.
def _resolve_dashboard_title(
    dashboard_id: int,
    superset_client: SupersetClient,
) -> str:
    try:
        _, dashboards = superset_client.get_dashboards()
        for d in dashboards:
            if d.get("id") == dashboard_id:
                return d.get("dashboard_title") or d.get("title") or str(dashboard_id)
    except Exception:
        pass
    return str(dashboard_id)
# #endregion _resolve_dashboard_title

# #endregion MaintenanceDashboardScanner
