# #region SupersetDashboardsCrudMixin [C:3] [TYPE Module] [SEMANTICS superset, dashboard, crud, import, export]
# @LAYER Infrastructure
# @BRIEF Dashboard CRUD mixin for SupersetClient — detail, export, import, delete.
# @RELATION DEPENDS_ON -> [SupersetClientBase]
# @RELATION DEPENDS_ON -> [SupersetDashboardsFiltersMixin]
# @RELATION DEPENDS_ON -> [SupersetChartsMixin]
import json
from pathlib import Path
import re
from typing import Any, cast

from requests import Response

from ..logger import belief_scope, logger as app_logger

app_logger = cast(Any, app_logger)
# #region SupersetDashboardsCrudMixin [C:3] [TYPE Class]
# @BRIEF Mixin providing dashboard detail resolution, export, import, and delete operations.
# @RELATION DEPENDS_ON -> [SupersetClientBase]
# @RELATION DEPENDS_ON -> [SupersetDashboardsFiltersMixin]
# @RELATION DEPENDS_ON -> [SupersetChartsMixin]
class SupersetDashboardsCrudMixin:
    # #region SupersetClientGetDashboardDetail [TYPE Function] [C:3]
    # @PURPOSE: Fetches detailed dashboard information including related charts and datasets.
    # @RELATION CALLS -> [SupersetClientGetDashboard]
    # @RELATION CALLS -> [SupersetClientGetChart]
    def get_dashboard_detail(self, dashboard_ref: int | str) -> dict:
        with belief_scope(
            "SupersetClient.get_dashboard_detail", f"ref={dashboard_ref}"
        ):
            dashboard_response = self.get_dashboard(dashboard_ref)
            dashboard_data = dashboard_response.get("result", dashboard_response)
            charts: list[dict] = []
            datasets: list[dict] = []
            # #region extract_dataset_id_from_form_data [TYPE Function]
            def extract_dataset_id_from_form_data(
                form_data: dict | None,
            ) -> int | None:
                if not isinstance(form_data, dict):
                    return None
                datasource = form_data.get("datasource")
                if isinstance(datasource, str):
                    matched = re.match(r"^(\d+)__", datasource)
                    if matched:
                        try:
                            return int(matched.group(1))
                        except ValueError:
                            return None
                if isinstance(datasource, dict):
                    ds_id = datasource.get("id")
                    try:
                        return int(ds_id) if ds_id is not None else None
                    except (TypeError, ValueError):
                        return None
                ds_id = form_data.get("datasource_id")
                try:
                    return int(ds_id) if ds_id is not None else None
                except (TypeError, ValueError):
                    return None
            # #endregion extract_dataset_id_from_form_data
            try:
                charts_response = self.network.request(
                    method="GET", endpoint=f"/dashboard/{dashboard_ref}/charts"
                )
                charts_payload = (
                    charts_response.get("result", [])
                    if isinstance(charts_response, dict)
                    else []
                )
                for chart_obj in charts_payload:
                    if not isinstance(chart_obj, dict):
                        continue
                    chart_id = chart_obj.get("id")
                    if chart_id is None:
                        continue
                    form_data = chart_obj.get("form_data")
                    if isinstance(form_data, str):
                        try:
                            form_data = json.loads(form_data)
                        except Exception:
                            form_data = {}
                    dataset_id = extract_dataset_id_from_form_data(
                        form_data
                    ) or chart_obj.get("datasource_id")
                    charts.append({
                        "id": int(chart_id),
                        "title": chart_obj.get("slice_name")
                        or chart_obj.get("name") or f"Chart {chart_id}",
                        "viz_type": (
                            form_data.get("viz_type")
                            if isinstance(form_data, dict) else None
                        ),
                        "dataset_id": int(dataset_id) if dataset_id is not None else None,
                        "last_modified": chart_obj.get("changed_on"),
                        "overview": chart_obj.get("description")
                        or (form_data.get("viz_type") if isinstance(form_data, dict) else None)
                        or "Chart",
                    })
            except Exception as e:
                app_logger.warning(
                    "[get_dashboard_detail][Warning] Failed to fetch dashboard charts: %s", e,
                )
            try:
                datasets_response = self.network.request(
                    method="GET", endpoint=f"/dashboard/{dashboard_ref}/datasets"
                )
                datasets_payload = (
                    datasets_response.get("result", [])
                    if isinstance(datasets_response, dict)
                    else []
                )
                for dataset_obj in datasets_payload:
                    if not isinstance(dataset_obj, dict):
                        continue
                    dataset_id = dataset_obj.get("id")
                    if dataset_id is None:
                        continue
                    db_payload = dataset_obj.get("database")
                    db_name = (
                        db_payload.get("database_name")
                        if isinstance(db_payload, dict) else None
                    )
                    table_name = (
                        dataset_obj.get("table_name")
                        or dataset_obj.get("datasource_name")
                        or dataset_obj.get("name") or f"Dataset {dataset_id}"
                    )
                    schema = dataset_obj.get("schema")
                    fq_name = f"{schema}.{table_name}" if schema else table_name
                    datasets.append({
                        "id": int(dataset_id),
                        "table_name": table_name,
                        "schema": schema,
                        "database": db_name or dataset_obj.get("database_name") or "Unknown",
                        "last_modified": dataset_obj.get("changed_on"),
                        "overview": fq_name,
                    })
            except Exception as e:
                app_logger.warning(
                    "[get_dashboard_detail][Warning] Failed to fetch dashboard datasets: %s", e,
                )
            # Fallback: derive chart IDs from layout metadata
            if not charts:
                raw_position_json = dashboard_data.get("position_json")
                chart_ids_from_position = set()
                if isinstance(raw_position_json, str) and raw_position_json:
                    try:
                        parsed_position = json.loads(raw_position_json)
                        chart_ids_from_position.update(
                            self._extract_chart_ids_from_layout(parsed_position)
                        )
                    except Exception:
                        app_logger.debug("Could not parse position JSON for dashboard %s", dashboard_ref)
                elif isinstance(raw_position_json, dict):
                    chart_ids_from_position.update(
                        self._extract_chart_ids_from_layout(raw_position_json)
                    )
                raw_json_metadata = dashboard_data.get("json_metadata")
                if isinstance(raw_json_metadata, str) and raw_json_metadata:
                    try:
                        parsed_metadata = json.loads(raw_json_metadata)
                        chart_ids_from_position.update(
                            self._extract_chart_ids_from_layout(parsed_metadata)
                        )
                    except Exception:
                        app_logger.debug("Could not parse json_metadata for dashboard %s", dashboard_ref)
                elif isinstance(raw_json_metadata, dict):
                    chart_ids_from_position.update(
                        self._extract_chart_ids_from_layout(raw_json_metadata)
                    )
                app_logger.info(
                    "[get_dashboard_detail][State] Extracted %s fallback chart IDs from layout (dashboard_id=%s)",
                    len(chart_ids_from_position), dashboard_ref,
                )
                for chart_id in sorted(chart_ids_from_position):
                    try:
                        chart_response = self.get_chart(int(chart_id))
                        chart_data = chart_response.get("result", chart_response)
                        charts.append({
                            "id": int(chart_id),
                            "title": chart_data.get("slice_name")
                            or chart_data.get("name") or f"Chart {chart_id}",
                            "viz_type": chart_data.get("viz_type"),
                            "dataset_id": chart_data.get("datasource_id"),
                            "last_modified": chart_data.get("changed_on"),
                            "overview": chart_data.get("description")
                            or chart_data.get("viz_type") or "Chart",
                        })
                    except Exception as e:
                        app_logger.warning(
                            "[get_dashboard_detail][Warning] Failed to resolve fallback chart %s: %s",
                            chart_id, e,
                        )
            # Backfill datasets from chart datasource IDs.
            dataset_ids_from_charts = {
                c.get("dataset_id") for c in charts if c.get("dataset_id") is not None
            }
            known_dataset_ids = {
                d.get("id") for d in datasets if d.get("id") is not None
            }
            missing_dataset_ids: list[int] = []
            for raw_dataset_id in dataset_ids_from_charts:
                if raw_dataset_id is None or raw_dataset_id in known_dataset_ids:
                    continue
                try:
                    missing_dataset_ids.append(int(raw_dataset_id))
                except (TypeError, ValueError):
                    continue
            for dataset_id in missing_dataset_ids:
                try:
                    dataset_response = self.get_dataset(int(dataset_id))
                    dataset_data = dataset_response.get("result", dataset_response)
                    db_payload = dataset_data.get("database")
                    db_name = (
                        db_payload.get("database_name")
                        if isinstance(db_payload, dict) else None
                    )
                    table_name = (
                        dataset_data.get("table_name") or f"Dataset {dataset_id}"
                    )
                    schema = dataset_data.get("schema")
                    fq_name = f"{schema}.{table_name}" if schema else table_name
                    datasets.append({
                        "id": int(dataset_id),
                        "table_name": table_name,
                        "schema": schema,
                        "database": db_name or "Unknown",
                        "last_modified": dataset_data.get("changed_on_utc")
                        or dataset_data.get("changed_on"),
                        "overview": fq_name,
                    })
                except Exception as e:
                    app_logger.warning(
                        "[get_dashboard_detail][Warning] Failed to resolve dataset %s: %s",
                        dataset_id, e,
                    )
            unique_charts = {chart["id"]: chart for chart in charts}
            unique_datasets = {dataset["id"]: dataset for dataset in datasets}
            resolved_dashboard_id = dashboard_data.get("id", dashboard_ref)
            return {
                "id": resolved_dashboard_id,
                "title": dashboard_data.get("dashboard_title")
                or dashboard_data.get("title") or f"Dashboard {resolved_dashboard_id}",
                "slug": dashboard_data.get("slug"),
                "url": dashboard_data.get("url"),
                "description": dashboard_data.get("description") or "",
                "last_modified": dashboard_data.get("changed_on_utc")
                or dashboard_data.get("changed_on"),
                "published": dashboard_data.get("published"),
                "charts": list(unique_charts.values()),
                "datasets": list(unique_datasets.values()),
                "chart_count": len(unique_charts),
                "dataset_count": len(unique_datasets),
            }
    # #endregion SupersetClientGetDashboardDetail
    # #region SupersetClientExportDashboard [TYPE Function] [C:3]
    # @PURPOSE: Экспортирует дашборд в виде ZIP-архива.
    # @SIDE_EFFECT  Performs network I/O to download archive.
    # @RELATION CALLS -> [APIClient]
    def export_dashboard(self, dashboard_id: int) -> tuple[bytes, str]:
        with belief_scope("export_dashboard"):
            app_logger.info(
                "[export_dashboard][Enter] Exporting dashboard %s.", dashboard_id
            )
            response = self.network.request(
                method="GET",
                endpoint="/dashboard/export/",
                params={"q": json.dumps([dashboard_id])},
                stream=True,
                raw_response=True,
            )
            response = cast(Response, response)
            self._validate_export_response(response, dashboard_id)
            filename = self._resolve_export_filename(response, dashboard_id)
            app_logger.reflect(
                f"Exported dashboard {dashboard_id} to {filename}.",
                extra={"src": "export_dashboard"},
            )
            return response.content, filename
    # #endregion SupersetClientExportDashboard
    # #region SupersetClientImportDashboard [TYPE Function] [C:3]
    # @PURPOSE: Импортирует дашборд из ZIP-файла.
    # @SIDE_EFFECT  Performs network I/O to upload archive.
    # @RELATION CALLS -> [SupersetClientDoImport]
    # @RELATION CALLS -> [APIClient]
    def import_dashboard(
        self,
        file_name: str | Path,
        dash_id: int | None = None,
        dash_slug: str | None = None,
    ) -> dict:
        with belief_scope("import_dashboard"):
            if file_name is None:
                raise ValueError("file_name cannot be None")
            file_path = str(file_name)
            self._validate_import_file(file_path)
            try:
                return self._do_import(file_path)
            except Exception as exc:
                app_logger.error(
                    "[import_dashboard][Failure] First import attempt failed: %s",
                    exc, exc_info=True,
                )
                if not self.delete_before_reimport:
                    raise
                target_id = self._resolve_target_id_for_delete(dash_id, dash_slug)
                if target_id is None:
                    app_logger.error(
                        "[import_dashboard][Failure] No ID available for delete-retry."
                    )
                    raise
                self.delete_dashboard(target_id)
                app_logger.info(
                    "[import_dashboard][State] Deleted dashboard ID %s, retrying import.",
                    target_id,
                )
                return self._do_import(file_path)
    # #endregion SupersetClientImportDashboard
    # #region SupersetClientDeleteDashboard [TYPE Function] [C:3]
    # @PURPOSE: Удаляет дашборд по его ID или slug.
    # @SIDE_EFFECT  Deletes resource from upstream Superset environment.
    # @RELATION CALLS -> [APIClient]
    def delete_dashboard(self, dashboard_id: int | str) -> None:
        with belief_scope("delete_dashboard"):
            app_logger.info(
                "[delete_dashboard][Enter] Deleting dashboard %s.", dashboard_id
            )
            response = self.network.request(
                method="DELETE", endpoint=f"/dashboard/{dashboard_id}"
            )
            response = cast(dict, response)
            if response.get("result", True) is not False:
                app_logger.info(
                    "[delete_dashboard][Success] Dashboard %s deleted.", dashboard_id
                )
            else:
                app_logger.warning(
                    "[delete_dashboard][Warning] Unexpected response while deleting %s: %s",
                    dashboard_id, response,
                )
    # #endregion SupersetClientDeleteDashboard
# #endregion SupersetDashboardsCrudMixin
# #endregion SupersetDashboardsCrudMixin
