# #region SupersetClientBase [C:3] [TYPE Module] [SEMANTICS superset, client, base, auth, pagination]
# @LAYER Infrastructure
# @BRIEF Base class for SupersetClient providing initialization, authentication, pagination, and import/export helpers.
# @RELATION DEPENDS_ON -> [get_filename_from_headers]
# @RELATION DEPENDS_ON -> [get_filename_from_headers]
# @RELATION DEPENDS_ON -> [get_filename_from_headers]
# @RELATION DEPENDS_ON -> [get_filename_from_headers]
# @RATIONALE Extracted magic number 100 into DEFAULT_PAGE_SIZE constant at module level. Value unchanged — Superset API caps page_size at 100.

from pathlib import Path
from typing import Any, Optional, cast
import zipfile

from requests import Response

DEFAULT_PAGE_SIZE = 100

from ..config_models import Environment
from ..logger import belief_scope, logger as app_logger
from ..utils.fileio import get_filename_from_headers
from ..utils.network import APIClient, SupersetAPIError

app_logger = cast(Any, app_logger)
# #region SupersetClientBase [C:3] [TYPE Class]
# @BRIEF Base class providing Superset client initialization, auth, pagination, and import/export plumbing.
# @RELATION DEPENDS_ON -> [ConfigModels]
# @RELATION DEPENDS_ON -> [APIClient]
# @RELATION DEPENDS_ON -> [SupersetAPIError]
class SupersetClientBase:
    # #region SupersetClientInit [TYPE Function] [C:3]
    # @PURPOSE Инициализирует клиент, проверяет конфигурацию и создает сетевой клиент.
    # @RELATION DEPENDS_ON -> [Environment]
    # @RELATION DEPENDS_ON -> [APIClient]
    def __init__(self, env: Environment):
        with belief_scope("SupersetClientInit"):
            app_logger.reason(
                "Initializing Superset client for environment",
                extra={"environment": getattr(env, "id", None), "env_name": env.name},
            )
        self.env = env
        # Construct auth payload expected by Superset API
        auth_payload = {
            "username": env.username,
            "password": env.password,
            "provider": "db",
            "refresh": "true",
        }
        self.network = APIClient(
            config={"base_url": env.url, "auth": auth_payload},
            verify_ssl=env.verify_ssl,
            timeout=env.timeout,
        )
        self.delete_before_reimport: bool = False
        app_logger.reflect(
            "Superset client initialized",
            extra={"environment": getattr(self.env, "id", None)},
        )
    # #endregion SupersetClientInit
    # #region SupersetClientAuthenticate [TYPE Function] [C:3]
    # @PURPOSE Authenticates the client using the configured credentials.
    # @RELATION CALLS -> [APIClient]
    def authenticate(self) -> dict[str, str]:
        with belief_scope("SupersetClientAuthenticate"):
            app_logger.reason(
                "Authenticating Superset client",
                extra={"environment": getattr(self.env, "id", None)},
            )
            tokens = self.network.authenticate()
            app_logger.reflect(
                "Superset client authentication completed",
                extra={
                    "environment": getattr(self.env, "id", None),
                    "token_keys": sorted(tokens.keys()),
                },
            )
            return tokens
    # #endregion SupersetClientAuthenticate
    @property
    # #region SupersetClientHeaders [TYPE Function] [C:1]
    # @PURPOSE Возвращает базовые HTTP-заголовки, используемые сетевым клиентом.
    def headers(self) -> dict:
        with belief_scope("headers"):
            return self.network.headers
    # #endregion SupersetClientHeaders
    # --- Pagination helpers ---
    # #region SupersetClientValidateQueryParams [TYPE Function] [C:1]
    # @PURPOSE Ensures query parameters have default page and page_size.
    def _validate_query_params(self, query: dict | None) -> dict:
        with belief_scope("_validate_query_params"):
            # Superset list endpoints commonly cap page_size at 100.
            # Using 100 avoids partial fetches when larger values are silently truncated.
            base_query = {"page": 0, "page_size": DEFAULT_PAGE_SIZE}
            return {**base_query, **(query or {})}
    # #endregion SupersetClientValidateQueryParams
    # #region SupersetClientFetchTotalObjectCount [TYPE Function] [C:1]
    # @PURPOSE Fetches the total number of items for a given endpoint.
    # @RELATION CALLS -> [APIClient]
    def _fetch_total_object_count(self, endpoint: str) -> int:
        with belief_scope("_fetch_total_object_count"):
            return self.network.fetch_paginated_count(
                endpoint=endpoint,
                query_params={"page": 0, "page_size": 1},
                count_field="count",
            )
    # #endregion SupersetClientFetchTotalObjectCount
    # #region SupersetClientFetchAllPages [TYPE Function] [C:1]
    # @PURPOSE Iterates through all pages to collect all data items.
    # @RELATION CALLS -> [APIClient]
    def _fetch_all_pages(self, endpoint: str, pagination_options: dict) -> list[dict]:
        with belief_scope("_fetch_all_pages"):
            return self.network.fetch_paginated_data(
                endpoint=endpoint, pagination_options=pagination_options
            )
    # #endregion SupersetClientFetchAllPages
    # --- Import/Export helpers ---
    # #region SupersetClientDoImport [TYPE Function] [C:1]
    # @PURPOSE Performs the actual multipart upload for import.
    # @RELATION CALLS -> [APIClient]
    def _do_import(self, file_name: str | Path) -> dict:
        with belief_scope("_do_import"):
            app_logger.debug(f"[_do_import][State] Uploading file: {file_name}")
            file_path = Path(file_name)
            if not file_path.exists():
                app_logger.error(
                    f"[_do_import][Failure] File does not exist: {file_name}"
                )
                raise FileNotFoundError(f"File does not exist: {file_name}")
            return self.network.upload_file(
                endpoint="/dashboard/import/",
                file_info={
                    "file_obj": file_path,
                    "file_name": file_path.name,
                    "form_field": "formData",
                },
                extra_data={"overwrite": "true"},
                timeout=self.env.timeout * 2,
            )
    # #endregion SupersetClientDoImport
    # #region SupersetClientValidateExportResponse [TYPE Function] [C:1]
    # @PURPOSE Validates that the export response is a non-empty ZIP archive.
    def _validate_export_response(self, response: Response, dashboard_id: int) -> None:
        with belief_scope("_validate_export_response"):
            content_type = response.headers.get("Content-Type", "")
            if "application/zip" not in content_type:
                raise SupersetAPIError(
                    f"Получен не ZIP-архив (Content-Type: {content_type})"
                )
            if not response.content:
                raise SupersetAPIError("Получены пустые данные при экспорте")
    # #endregion SupersetClientValidateExportResponse
    # #region SupersetClientResolveExportFilename [TYPE Function] [C:1]
    # @PURPOSE Determines the filename for an exported dashboard.
    def _resolve_export_filename(self, response: Response, dashboard_id: int) -> str:
        with belief_scope("_resolve_export_filename"):
            filename = get_filename_from_headers(dict(response.headers))
            if not filename:
                from datetime import datetime
                timestamp = datetime.now().strftime("%Y%m%dT%H%M%S")
                filename = f"dashboard_export_{dashboard_id}_{timestamp}.zip"
                app_logger.warning(
                    "[_resolve_export_filename][Warning] Generated filename: %s",
                    filename,
                )
            return filename
    # #endregion SupersetClientResolveExportFilename
    # #region SupersetClientValidateImportFile [TYPE Function] [C:1]
    # @PURPOSE Validates that the file to be imported is a valid ZIP with metadata.yaml.
    def _validate_import_file(self, zip_path: str | Path) -> None:
        with belief_scope("_validate_import_file"):
            path = Path(zip_path)
            if not path.exists():
                raise FileNotFoundError(f"Файл {zip_path} не существует")
            if not zipfile.is_zipfile(path):
                raise SupersetAPIError(f"Файл {zip_path} не является ZIP-архивом")
            with zipfile.ZipFile(path, "r") as zf:
                if not any(n.endswith("metadata.yaml") for n in zf.namelist()):
                    raise SupersetAPIError(
                        f"Архив {zip_path} не содержит 'metadata.yaml'"
                    )
    # #endregion SupersetClientValidateImportFile
    # #region SupersetClientResolveTargetIdForDelete [TYPE Function] [C:1]
    # @PURPOSE Resolves a dashboard ID from either an ID or a slug.
    # @RELATION CALLS -> [SupersetClientGetDashboards]
    def _resolve_target_id_for_delete(
        self, dash_id: int | None, dash_slug: str | None
    ) -> int | None:
        with belief_scope("_resolve_target_id_for_delete"):
            if dash_id is not None:
                return dash_id
            if dash_slug is not None:
                app_logger.debug(
                    "[_resolve_target_id_for_delete][State] Resolving ID by slug '%s'.",
                    dash_slug,
                )
                try:
                    _, candidates = self.get_dashboards(
                        query={
                            "filters": [{"col": "slug", "op": "eq", "value": dash_slug}]
                        }
                    )
                    if candidates:
                        target_id = candidates[0]["id"]
                        app_logger.debug(
                            "[_resolve_target_id_for_delete][Success] Resolved slug to ID %s.",
                            target_id,
                        )
                        return target_id
                except Exception as e:
                    app_logger.warning(
                        "[_resolve_target_id_for_delete][Warning] Could not resolve slug '%s' to ID: %s",
                        dash_slug,
                        e,
                    )
            return None
    # #endregion SupersetClientResolveTargetIdForDelete
    # #region SupersetClientGetAllResources [TYPE Function] [C:3]
    # @PURPOSE Fetches all resources of a given type with id, uuid, and name columns.
    # @RELATION CALLS -> [SupersetClientFetchAllPages]
    def get_all_resources(
        self, resource_type: str, since_dttm: Optional["datetime"] = None
    ) -> list[dict]:
        with belief_scope(
            "SupersetClient.get_all_resources",
            f"type={resource_type}, since={since_dttm}",
        ):
            column_map = {
                "chart": {
                    "endpoint": "/chart/",
                    "columns": ["id", "uuid", "slice_name"],
                },
                "dataset": {
                    "endpoint": "/dataset/",
                    "columns": ["id", "uuid", "table_name"],
                },
                "dashboard": {
                    "endpoint": "/dashboard/",
                    "columns": ["id", "uuid", "slug", "dashboard_title"],
                },
            }
            config = column_map.get(resource_type)
            if not config:
                app_logger.warning(
                    "[get_all_resources][Warning] Unknown resource type: %s",
                    resource_type,
                )
                return []
            query = {"columns": config["columns"]}
            if since_dttm:
                import math
                # Use int milliseconds to be safe
                timestamp_ms = math.floor(since_dttm.timestamp() * 1000)
                query["filters"] = [
                    {"col": "changed_on_dttm", "opr": "gt", "value": timestamp_ms}
                ]
            validated = self._validate_query_params(query)
            data = self._fetch_all_pages(
                endpoint=config["endpoint"],
                pagination_options={"base_query": validated, "results_field": "result"},
            )
            app_logger.reflect(
                f"Fetched {len(data)} {resource_type} resources.",
                extra={"src": "get_all_resources"},
            )
            return data
    # #endregion SupersetClientGetAllResources
# #endregion SupersetClientBase
# #endregion SupersetClientBase
