# #region DashboardDetailRoutes [C:3] [TYPE Module] [SEMANTICS fastapi, dashboard, api, transform, search, task]
# @BRIEF Dashboard detail, db-mappings, task history, thumbnail route handlers.
# @LAYER API
# @RELATION DEPENDS_ON -> [EXT:frontend:DashboardRouter]]
# @RELATION DEPENDS_ON -> [DashboardSchemas]
# @RELATION DEPENDS_ON -> [DashboardHelpers]
# @RELATION DEPENDS_ON -> [DashboardProjection]

import re
from typing import Any
from urllib.parse import urlparse

from fastapi import Depends, HTTPException, Query, Response
from fastapi.responses import JSONResponse

from src.core.async_superset_client import AsyncSupersetClient
from src.core.logger import belief_scope, logger
from src.core.superset_client import SupersetClient
from src.core.utils.network import DashboardNotFoundError
from src.dependencies import (
    get_config_manager,
    get_mapping_service,
    get_task_manager,
    has_permission,
)

from ._helpers import (
    _resolve_dashboard_id_from_ref,
    _resolve_dashboard_id_from_ref_async,
)
from ._projection import (
    _task_matches_dashboard,
)
from ._router import router
from ._schemas import (
    DashboardDetailResponse,
    DashboardTaskHistoryItem,
    DashboardTaskHistoryResponse,
    DatabaseMapping,
    DatabaseMappingsResponse,
)


# #region get_database_mappings [C:2] [TYPE Function]
# @BRIEF Get database mapping suggestions between source and target environments
# @PRE  User has permission plugin:migration:read
# @PRE  source_env_id and target_env_id are valid environment IDs
# @POST  Returns list of suggested database mappings with confidence scores
# @RELATION CALLS -> [EXT:method:MappingService:get_suggestions]
@router.get("/db-mappings", response_model=DatabaseMappingsResponse)
async def get_database_mappings(
    source_env_id: str,
    target_env_id: str,
    config_manager=Depends(get_config_manager),
    mapping_service=Depends(get_mapping_service),
    _=Depends(has_permission("plugin:migration", "READ")),
):
    with belief_scope(
        "get_database_mappings", f"source={source_env_id}, target={target_env_id}"
    ):
        environments = config_manager.get_environments()
        source_env = next((e for e in environments if e.id == source_env_id), None)
        target_env = next((e for e in environments if e.id == target_env_id), None)

        if not source_env:
            logger.error(
                f"[get_database_mappings][Coherence:Failed] Source environment not found: {source_env_id}"
            )
            raise HTTPException(status_code=404, detail="Source environment not found")
        if not target_env:
            logger.error(
                f"[get_database_mappings][Coherence:Failed] Target environment not found: {target_env_id}"
            )
            raise HTTPException(status_code=404, detail="Target environment not found")

        try:
            suggestions = await mapping_service.get_suggestions(
                source_env_id, target_env_id
            )

            mappings = [
                DatabaseMapping(
                    source_db=s.get("source_db", ""),
                    target_db=s.get("target_db", ""),
                    source_db_uuid=s.get("source_db_uuid"),
                    target_db_uuid=s.get("target_db_uuid"),
                    confidence=s.get("confidence", 0.0),
                )
                for s in suggestions
            ]

            logger.info(
                f"[get_database_mappings][Coherence:OK] Returning {len(mappings)} database mapping suggestions"
            )

            return DatabaseMappingsResponse(mappings=mappings)

        except Exception as e:
            logger.error(
                f"[get_database_mappings][Coherence:Failed] Failed to get database mappings: {e}"
            )
            raise HTTPException(
                status_code=503, detail=f"Failed to get database mappings: {e!s}"
            )


# #endregion get_database_mappings


# #region get_dashboard_detail [C:2] [TYPE Function]
# @BRIEF Fetch detailed dashboard info with related charts and datasets
# @PRE  env_id must be valid and dashboard ref (slug or id) must exist
# @POST  Returns dashboard detail payload for overview page
# @RELATION CALLS -> [AsyncSupersetClient]
@router.get("/{dashboard_ref}", response_model=DashboardDetailResponse)
async def get_dashboard_detail(
    dashboard_ref: str,
    env_id: str,
    config_manager=Depends(get_config_manager),
    _=Depends(has_permission("plugin:migration", "READ")),
):
    with belief_scope(
        "get_dashboard_detail", f"dashboard_ref={dashboard_ref}, env_id={env_id}"
    ):
        environments = config_manager.get_environments()
        env = next((e for e in environments if e.id == env_id), None)
        if not env:
            logger.error(
                f"[get_dashboard_detail][Coherence:Failed] Environment not found: {env_id}"
            )
            raise HTTPException(status_code=404, detail="Environment not found")

        try:
            sync_client = SupersetClient(env)
            dashboard_id = _resolve_dashboard_id_from_ref(dashboard_ref, sync_client)
            detail = sync_client.get_dashboard_detail(dashboard_id)
            logger.info(
                f"[get_dashboard_detail][Coherence:OK] Dashboard ref={dashboard_ref} resolved_id={dashboard_id}: {detail.get('chart_count', 0)} charts, {detail.get('dataset_count', 0)} datasets"
            )
            return DashboardDetailResponse(**detail)
        except HTTPException:
            raise
        except Exception as e:
            logger.error(
                f"[get_dashboard_detail][Coherence:Failed] Failed to fetch dashboard detail: {e}"
            )
            raise HTTPException(
                status_code=503, detail=f"Failed to fetch dashboard detail: {e!s}"
            )


# #endregion get_dashboard_detail


# #region get_dashboard_tasks_history [C:2] [TYPE Function]
# @BRIEF Returns history of backup and LLM validation tasks for a dashboard.
# @PRE  dashboard ref (slug or id) is valid.
# @POST  Response contains sorted task history (newest first).
@router.get("/{dashboard_ref}/tasks", response_model=DashboardTaskHistoryResponse)
async def get_dashboard_tasks_history(
    dashboard_ref: str,
    env_id: str | None = None,
    limit: int = Query(20, ge=1, le=100),
    config_manager=Depends(get_config_manager),
    task_manager=Depends(get_task_manager),
    _=Depends(has_permission("tasks", "READ")),
):
    with belief_scope(
        "get_dashboard_tasks_history",
        f"dashboard_ref={dashboard_ref}, env_id={env_id}, limit={limit}",
    ):
        dashboard_id: int | None = None
        client: AsyncSupersetClient | None = None
        try:
            if dashboard_ref.isdigit():
                dashboard_id = int(dashboard_ref)
            elif env_id:
                environments = config_manager.get_environments()
                env = next((e for e in environments if e.id == env_id), None)
                if not env:
                    logger.error(
                        f"[get_dashboard_tasks_history][Coherence:Failed] Environment not found: {env_id}"
                    )
                    raise HTTPException(status_code=404, detail="Environment not found")
                client = AsyncSupersetClient(env)
                dashboard_id = await _resolve_dashboard_id_from_ref_async(
                    dashboard_ref, client
                )
            else:
                logger.error(
                    "[get_dashboard_tasks_history][Coherence:Failed] Non-numeric dashboard ref requires env_id"
                )
                raise HTTPException(
                    status_code=400,
                    detail="env_id is required when dashboard reference is a slug",
                )

            matching_tasks = []
            for task in task_manager.get_all_tasks():
                if _task_matches_dashboard(task, dashboard_id, env_id):
                    matching_tasks.append(task)

            def _sort_key(task_obj: Any) -> str:
                return str(getattr(task_obj, "started_at", "") or "") or str(
                    getattr(task_obj, "finished_at", "") or ""
                )

            matching_tasks.sort(key=_sort_key, reverse=True)
            selected = matching_tasks[:limit]

            items = []
            for task in selected:
                result = getattr(task, "result", None)
                summary = None
                validation_status = None
                if isinstance(result, dict):
                    raw_validation_status = result.get("status")
                    if raw_validation_status is not None:
                        validation_status = str(raw_validation_status)
                    summary = (
                        result.get("summary")
                        or result.get("status")
                        or result.get("message")
                    )
                params = getattr(task, "params", {}) or {}
                items.append(
                    DashboardTaskHistoryItem(
                        id=str(getattr(task, "id", "")),
                        plugin_id=str(getattr(task, "plugin_id", "")),
                        status=str(getattr(task, "status", "")),
                        validation_status=validation_status,
                        started_at=getattr(task, "started_at", None).isoformat()
                        if getattr(task, "started_at", None)
                        else None,
                        finished_at=getattr(task, "finished_at", None).isoformat()
                        if getattr(task, "finished_at", None)
                        else None,
                        env_id=str(params.get("environment_id") or params.get("env"))
                        if (params.get("environment_id") or params.get("env"))
                        else None,
                        summary=summary,
                    )
                )

            logger.info(
                f"[get_dashboard_tasks_history][Coherence:OK] Found {len(items)} tasks for dashboard_ref={dashboard_ref}, dashboard_id={dashboard_id}"
            )
            return DashboardTaskHistoryResponse(dashboard_id=dashboard_id, items=items)
        finally:
            if client is not None:
                await client.aclose()


# #endregion get_dashboard_tasks_history


# #region get_dashboard_thumbnail [C:3] [TYPE Function]
# @BRIEF Proxies Superset dashboard thumbnail with cache support.
# @RELATION CALLS -> [AsyncSupersetClient]
# @PRE  env_id must exist.
# @POST  Returns image bytes or 202 when thumbnail is being prepared by Superset.
@router.get("/{dashboard_ref}/thumbnail")
async def get_dashboard_thumbnail(
    dashboard_ref: str,
    env_id: str,
    force: bool = Query(False),
    config_manager=Depends(get_config_manager),
    _=Depends(has_permission("plugin:migration", "READ")),
):
    with belief_scope(
        "get_dashboard_thumbnail",
        f"dashboard_ref={dashboard_ref}, env_id={env_id}, force={force}",
    ):
        environments = config_manager.get_environments()
        env = next((e for e in environments if e.id == env_id), None)
        if not env:
            logger.error(
                f"[get_dashboard_thumbnail][Coherence:Failed] Environment not found: {env_id}"
            )
            raise HTTPException(status_code=404, detail="Environment not found")

        try:
            client = SupersetClient(env)
            dashboard_id = _resolve_dashboard_id_from_ref(dashboard_ref, client)
            digest = None
            thumb_endpoint = None

            try:
                screenshot_payload = client.network.request(
                    method="POST",
                    endpoint=f"/dashboard/{dashboard_id}/cache_dashboard_screenshot/",
                )
                payload = (
                    screenshot_payload.get("result", screenshot_payload)
                    if isinstance(screenshot_payload, dict)
                    else {}
                )
                image_url = (
                    payload.get("image_url", "") if isinstance(payload, dict) else ""
                )
                if isinstance(image_url, str) and image_url:
                    matched = re.search(
                        r"/dashboard/\d+/(?:thumbnail|screenshot)/([^/]+)/?$",
                        image_url,
                    )
                    if matched:
                        digest = matched.group(1)
            except DashboardNotFoundError:
                logger.warning(
                    "[get_dashboard_thumbnail][Fallback] cache_dashboard_screenshot endpoint unavailable, fallback to dashboard.thumbnail_url"
                )

            if not digest:
                dashboard_payload = client.network.request(
                    method="GET",
                    endpoint=f"/dashboard/{dashboard_id}",
                )
                dashboard_data = (
                    dashboard_payload.get("result", dashboard_payload)
                    if isinstance(dashboard_payload, dict)
                    else {}
                )
                thumbnail_url = (
                    dashboard_data.get("thumbnail_url", "")
                    if isinstance(dashboard_data, dict)
                    else ""
                )
                if isinstance(thumbnail_url, str) and thumbnail_url:
                    parsed = urlparse(thumbnail_url)
                    parsed_path = parsed.path or thumbnail_url
                    if parsed_path.startswith("/api/v1/"):
                        parsed_path = parsed_path[len("/api/v1") :]
                    thumb_endpoint = parsed_path
                    matched = re.search(
                        r"/dashboard/\d+/(?:thumbnail|screenshot)/([^/]+)/?$",
                        parsed_path,
                    )
                    if matched:
                        digest = matched.group(1)

            if not thumb_endpoint:
                thumb_endpoint = (
                    f"/dashboard/{dashboard_id}/thumbnail/{digest or 'latest'}/"
                )

            thumb_response = client.network.request(
                method="GET",
                endpoint=thumb_endpoint,
                raw_response=True,
                allow_redirects=True,
            )

            if thumb_response.status_code == 202:
                payload_202: dict[str, Any] = {}
                try:
                    payload_202 = thumb_response.json()
                except Exception:
                    payload_202 = {"message": "Thumbnail is being generated"}
                return JSONResponse(status_code=202, content=payload_202)

            content_type = thumb_response.headers.get("Content-Type", "image/png")
            return Response(content=thumb_response.content, media_type=content_type)
        except DashboardNotFoundError as e:
            logger.error(
                f"[get_dashboard_thumbnail][Coherence:Failed] Dashboard not found for thumbnail: {e}"
            )
            raise HTTPException(status_code=404, detail="Dashboard thumbnail not found")
        except HTTPException:
            raise
        except Exception as e:
            logger.error(
                f"[get_dashboard_thumbnail][Coherence:Failed] Failed to fetch dashboard thumbnail: {e}"
            )
            raise HTTPException(
                status_code=503,
                detail=f"Failed to fetch dashboard thumbnail: {e!s}",
            )


# #endregion get_dashboard_thumbnail
# #endregion DashboardDetailRoutes
