"""Pydantic models for the dashboard widget system.

Widgets are either:
* **service-bound** — reference a ``service_id`` and a ``widget_kind`` declared
  by that service's definition (Prometheus metric, Jellyfin
  activity, SSH task output); or
* **built-in** — ``service_id`` is null and ``widget_kind`` is one of the
  service-less kinds (backups, static).
"""

from __future__ import annotations

from typing import Any

from pydantic import BaseModel, Field, field_validator, model_validator

FORBIDDEN_CONFIG_KEYS = {
    "password",
    "token",
    "secret",
    "api_key",
    "apikey",
    "private_key",
    "passphrase",
    "credential",
}


def _looks_secret(value: Any) -> bool:
    """Heuristic to detect values that look like secrets/tokens."""
    if not isinstance(value, str) or not value.strip():
        return False
    lowered = value.lower()
    if value.startswith("sk-") or value.startswith("eyJ"):
        return True
    if len(value) > 64 and lowered.isalnum():
        return True
    return False


def _validate_config_keys(config: dict[str, Any]) -> dict[str, Any]:
    """Recursively reject credential keys and secret-looking values."""
    for key, value in config.items():
        if key.lower() in FORBIDDEN_CONFIG_KEYS:
            raise ValueError(f"Credential key '{key}' is not allowed in widget config")
        if _looks_secret(value):
            raise ValueError(f"Value for '{key}' looks like a secret")
        if isinstance(value, dict):
            _validate_config_keys(value)
        elif isinstance(value, list):
            for item in value:
                if isinstance(item, dict):
                    _validate_config_keys(item)
    return config


class _WidgetInstanceBase(BaseModel):
    """Shared fields between input and output widget models."""

    service_id: str | None = None
    widget_kind: str = Field(..., min_length=1)
    title: str = Field(..., min_length=1)
    config: dict[str, Any] = Field(default_factory=dict)
    enabled: bool = True
    sort_order: int = Field(default=0, ge=0)

    @field_validator("config")
    @classmethod
    def reject_credential_keys(cls, value: dict[str, Any]) -> dict[str, Any]:
        return _validate_config_keys(value or {})

    @model_validator(mode="after")
    def _validate_kind(self) -> "_WidgetInstanceBase":
        # The kind must be non-empty (Field enforces it); service_id may be None
        # for built-ins. Deeper validation happens in the router against the
        # service definition / built-in registry.
        return self


class WidgetInstanceInput(_WidgetInstanceBase):
    """Payload for creating or updating a widget instance."""

    id: str | None = None


class WidgetInstance(_WidgetInstanceBase):
    """Persisted widget instance returned by the API."""

    id: str
    created_at: int
    updated_at: int


class BuiltinWidgetKindInfo(BaseModel):
    """Metadata about a built-in (service-less) widget kind."""

    kind: str
    name: str
    description: str
    config_schema: dict[str, Any]
    default_config: dict[str, Any]
    refresh_interval_ms: int


class WidgetDataResponse(BaseModel):
    """Response from the per-widget data endpoint."""

    widget_id: str
    data: dict[str, Any] | None = None
    error: str | None = None
    fetched_at: int
