"""REST API for dashboard widget instances.

Widgets are either service-bound (``service_id`` + ``widget_kind`` from the
service definition) or built-in (``service_id`` is null; ``widget_kind`` is one
of the service-less kinds exposed by ``GET /api/widgets/builtin``).
"""

from __future__ import annotations

import logging
import time
from typing import Any

from fastapi import APIRouter, Depends, HTTPException, status
from pydantic import BaseModel

from media_library_viewer_api.dependencies import get_settings_store
from media_library_viewer_api.integrations.base import validate_config
from media_library_viewer_api.integrations.registry import get_service_definition
from media_library_viewer_api.models.widgets import (
    BuiltinWidgetKindInfo,
    WidgetDataResponse,
    WidgetInstance,
    WidgetInstanceInput,
)
from media_library_viewer_api.services.settings_store import SettingsStore
from media_library_viewer_api.widgets.builtin import (
    BUILTIN_WIDGET_KINDS,
    is_builtin_kind,
    validate_builtin_config,
)
from media_library_viewer_api.widgets.sources import (
    build_service_record,
    get_builtin_adapter,
    get_service_adapter,
)


class WidgetReferenceCreate(BaseModel):
    """Payload for creating a widget reference (live-link)."""

    dashboard_scope: str
    widget_id: str
    sort_order: int = 0


router = APIRouter(prefix="/api/widgets", tags=["widgets"])

logger = logging.getLogger(__name__)


def _validate_widget_input(body: WidgetInstanceInput, store: SettingsStore) -> None:
    """Validate widget_kind + config against the service definition or built-ins."""
    if body.service_id:
        service = store.get_service(body.service_id)
        if not service:
            raise HTTPException(
                status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
                detail=f"Service {body.service_id} not found",
            )
        definition = get_service_definition(service["service_type"])
        if definition is None:
            raise HTTPException(
                status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
                detail=f"Unknown service type: {service['service_type']}",
            )
        widget_kind = definition.widget_kind(body.widget_kind)
        if widget_kind is None:
            raise HTTPException(
                status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
                detail=(f"Service type '{service['service_type']}' does not provide widget kind '{body.widget_kind}'"),
            )
        if widget_kind.config_model is not None:
            try:
                validate_config(widget_kind.config_model, body.config)
            except Exception as exc:
                raise HTTPException(
                    status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
                    detail=f"Invalid widget config: {exc}",
                ) from exc
    else:
        if not is_builtin_kind(body.widget_kind):
            raise HTTPException(
                status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
                detail=(
                    f"Unknown built-in widget kind '{body.widget_kind}' (set service_id for service-bound widgets)"
                ),
            )
        try:
            validate_builtin_config(body.widget_kind, body.config)
        except Exception as exc:
            raise HTTPException(
                status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
                detail=f"Invalid widget config: {exc}",
            ) from exc


@router.get("/builtin")
def list_builtin_kinds() -> list[BuiltinWidgetKindInfo]:
    """Return metadata for service-less built-in widget kinds."""
    return [
        BuiltinWidgetKindInfo(
            kind=wk.kind,
            name=wk.name,
            description=wk.description,
            config_schema=wk.config_schema,
            default_config=wk.default_config,
            refresh_interval_ms=wk.refresh_interval_ms,
        )
        for wk in BUILTIN_WIDGET_KINDS.values()
    ]


@router.get("/instances")
def list_instances(
    service_id: str | None = None,
    scope: str | None = None,
    store: SettingsStore = Depends(get_settings_store),
) -> list[dict[str, Any]]:
    """Return widget instances, optionally filtered.

    - ``?service_id=X``: only widgets for service X.
    - ``?scope=dashboard``: only widgets with NULL service_id.
    - ``?scope=service``: only widgets with a non-null service_id.
    - No params: all widgets (backward-compatible).
    """
    return [WidgetInstance(**widget).model_dump() for widget in store.list_widgets(service_id=service_id, scope=scope)]


@router.post("/instances", status_code=status.HTTP_201_CREATED)
def create_instance(
    body: WidgetInstanceInput,
    store: SettingsStore = Depends(get_settings_store),
) -> dict[str, Any]:
    """Create a new widget instance."""
    _validate_widget_input(body, store)
    widget = store.upsert_widget(body.model_dump())
    return WidgetInstance(**widget).model_dump()


@router.put("/instances/{widget_id}")
def update_instance(
    widget_id: str,
    body: WidgetInstanceInput,
    store: SettingsStore = Depends(get_settings_store),
) -> dict[str, Any]:
    """Update an existing widget instance."""
    existing = store.get_widget(widget_id)
    if not existing:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Widget not found")
    if body.id is not None and body.id != widget_id:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="ID in path does not match ID in body",
        )
    _validate_widget_input(body, store)
    widget = store.upsert_widget(body.model_dump(), widget_id)
    return WidgetInstance(**widget).model_dump()


@router.delete("/instances/{widget_id}")
def delete_instance(
    widget_id: str,
    store: SettingsStore = Depends(get_settings_store),
) -> dict[str, str]:
    """Delete a widget instance."""
    existing = store.get_widget(widget_id)
    if not existing:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Widget not found")
    store.delete_widget(widget_id)
    return {"status": "deleted"}


@router.get("/instances/{widget_id}/data")
async def fetch_data(
    widget_id: str,
    store: SettingsStore = Depends(get_settings_store),
) -> dict[str, Any]:
    """Fetch widget data through the registered source adapter."""
    widget = store.get_widget(widget_id)
    if not widget:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Widget not found")

    service_id = widget.get("service_id")
    widget_kind = widget.get("widget_kind") or ""

    service: Any = None
    if service_id:
        service_row = store.get_service(service_id)
        if not service_row:
            return WidgetDataResponse(
                widget_id=widget_id,
                error=f"Service {service_id} not found",
                fetched_at=int(time.time()),
            ).model_dump()
        if not service_row.get("enabled", True):
            return WidgetDataResponse(
                widget_id=widget_id,
                error="Service is disabled",
                fetched_at=int(time.time()),
            ).model_dump()
        adapter = get_service_adapter(service_row["service_type"])
        if adapter is None:
            return WidgetDataResponse(
                widget_id=widget_id,
                error=f"No adapter for service type {service_row['service_type']}",
                fetched_at=int(time.time()),
            ).model_dump()
        service = build_service_record(store, service_row)
    else:
        adapter = get_builtin_adapter(widget_kind)
        if adapter is None:
            return WidgetDataResponse(
                widget_id=widget_id,
                error=f"Unknown built-in widget kind: {widget_kind}",
                fetched_at=int(time.time()),
            ).model_dump()

    try:
        data = await adapter.fetch(service, widget_kind, widget.get("config") or {})
    except Exception as exc:  # pragma: no cover - defensive
        logger.exception("Unhandled adapter exception widget_id=%s", widget_id)
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail="Widget data fetch failed",
        ) from exc

    return WidgetDataResponse(
        widget_id=widget_id,
        data=data if "error" not in data else None,
        error=data.get("error"),
        fetched_at=int(time.time()),
    ).model_dump()


# ---------------------------------------------------------------------------
# Widget references (live-link widgets across dashboards)
# ---------------------------------------------------------------------------


@router.get("/references")
def list_references(
    dashboard_scope: str,
    store: SettingsStore = Depends(get_settings_store),
) -> list[dict[str, Any]]:
    """List widget references for a dashboard scope."""
    return store.list_widget_references(dashboard_scope)


@router.post("/references", status_code=status.HTTP_201_CREATED)
def create_reference(
    body: WidgetReferenceCreate,
    store: SettingsStore = Depends(get_settings_store),
) -> dict[str, Any]:
    """Create a widget reference (live-link) on a dashboard."""
    try:
        return store.create_widget_reference(body.dashboard_scope, body.widget_id, body.sort_order)
    except ValueError as exc:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc


@router.delete("/references/{reference_id}")
def delete_reference(
    reference_id: str,
    store: SettingsStore = Depends(get_settings_store),
) -> dict[str, str]:
    """Remove a widget reference from a dashboard."""
    store.delete_widget_reference(reference_id)
    return {"status": "deleted"}


@router.put("/references/{reference_id}")
def update_reference(
    reference_id: str,
    sort_order: int,
    store: SettingsStore = Depends(get_settings_store),
) -> dict[str, Any]:
    """Update a widget reference's sort_order (per-dashboard reordering)."""
    try:
        return store.update_widget_reference(reference_id, sort_order)
    except ValueError as exc:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc


@router.post("/references/{reference_id}/detach")
def detach_reference(
    reference_id: str,
    store: SettingsStore = Depends(get_settings_store),
) -> dict[str, Any]:
    """Clone the referenced widget into a standalone instance and remove the reference."""
    try:
        cloned = store.detach_widget_reference(reference_id, "")
        return WidgetInstance(**cloned).model_dump()
    except ValueError as exc:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc
