"""Admin cache-settings router.

Exposes endpoints to read the current frontend-cache configuration, toggle it
on/off, and clear the cache. Each mutating call writes an audit-log entry via
:func:`services.audit_log.record_admin_event`.

Cache state is persisted in the generic ``site_settings`` table under a single
JSON-encoded row with the key ``"frontend_cache"``.
"""

from __future__ import annotations

import json
import logging
import time
from typing import Any, Dict, Optional

from fastapi import APIRouter, Body, Depends, HTTPException, Request
from pydantic import BaseModel
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from core.database import get_db
from dependencies.auth import get_admin_user
from models.site_settings import Site_settings
from schemas.auth import UserResponse
from services import audit_log as audit_log_service

logger = logging.getLogger(__name__)

router = APIRouter(prefix="/api/v1/admin/cache", tags=["admin-cache"])

SETTINGS_KEY = "frontend_cache"
DEFAULT_CONFIG: Dict[str, Any] = {
    "enabled": True,
    "last_cleared_at": None,
    "cache_version": 1,
}


class CacheConfig(BaseModel):
    """Response schema for the frontend-cache configuration."""

    enabled: bool
    last_cleared_at: Optional[float] = None
    cache_version: int = 1


class CacheToggleRequest(BaseModel):
    """Request body for toggling the cache on/off."""

    enabled: bool


async def _get_or_create_row(db: AsyncSession) -> Site_settings:
    """Fetch the cache-settings row, creating it with defaults on first use."""
    result = await db.execute(
        select(Site_settings).where(Site_settings.key == SETTINGS_KEY)
    )
    row = result.scalar_one_or_none()
    if row is None:
        row = Site_settings(
            key=SETTINGS_KEY,
            value_json=json.dumps(DEFAULT_CONFIG),
        )
        db.add(row)
        await db.commit()
        await db.refresh(row)
    return row


def _parse_config(row: Site_settings) -> Dict[str, Any]:
    """Parse the row's JSON payload, falling back to defaults on corruption."""
    try:
        data = json.loads(row.value_json or "{}")
        if not isinstance(data, dict):
            raise ValueError("cache config is not an object")
    except (ValueError, TypeError):
        logger.warning("[cache_settings] Corrupt value_json, resetting to defaults")
        data = {}
    merged = {**DEFAULT_CONFIG, **data}
    # Coerce types so the response is always well-formed.
    merged["enabled"] = bool(merged.get("enabled", True))
    merged["cache_version"] = int(merged.get("cache_version", 1) or 1)
    lca = merged.get("last_cleared_at")
    merged["last_cleared_at"] = float(lca) if isinstance(lca, (int, float)) else None
    return merged


@router.get("", response_model=CacheConfig)
async def get_cache_config(
    db: AsyncSession = Depends(get_db),
    current_user: UserResponse = Depends(get_admin_user),
) -> CacheConfig:
    """Return the current frontend cache configuration."""
    row = await _get_or_create_row(db)
    cfg = _parse_config(row)
    return CacheConfig(**cfg)


@router.put("/toggle", response_model=CacheConfig)
async def toggle_cache(
    payload: CacheToggleRequest,
    request: Request,
    db: AsyncSession = Depends(get_db),
    current_user: UserResponse = Depends(get_admin_user),
) -> CacheConfig:
    """Enable or disable the frontend cache."""
    row = await _get_or_create_row(db)
    cfg = _parse_config(row)
    previous = cfg["enabled"]

    cfg["enabled"] = bool(payload.enabled)
    row.value_json = json.dumps(cfg)

    try:
        await db.commit()
        await db.refresh(row)
    except Exception as exc:  # noqa: BLE001
        await db.rollback()
        logger.exception("[cache_settings] Failed to persist toggle: %s", exc)
        raise HTTPException(status_code=500, detail="Failed to update cache setting")

    await audit_log_service.record_admin_event(
        db,
        action=audit_log_service.ACTION_CACHE_TOGGLED,
        admin_user_id=getattr(current_user, "id", None),
        admin_email=getattr(current_user, "email", None),
        details={
            "previous": previous,
            "enabled": cfg["enabled"],
        },
        request=request,
    )

    return CacheConfig(**cfg)


@router.post("/clear", response_model=CacheConfig)
async def clear_cache(
    request: Request,
    db: AsyncSession = Depends(get_db),
    current_user: UserResponse = Depends(get_admin_user),
    payload: Optional[Dict[str, Any]] = Body(default=None),
) -> CacheConfig:
    """Bump the cache-busting version and record a timestamp.

    Frontends read ``cache_version`` on boot and use it to invalidate any
    stored caches (e.g. react-query caches, localStorage keys). Incrementing
    this value is the canonical way to clear caches across clients.
    """
    row = await _get_or_create_row(db)
    cfg = _parse_config(row)
    cfg["cache_version"] = int(cfg.get("cache_version", 1)) + 1
    cfg["last_cleared_at"] = time.time()
    row.value_json = json.dumps(cfg)

    try:
        await db.commit()
        await db.refresh(row)
    except Exception as exc:  # noqa: BLE001
        await db.rollback()
        logger.exception("[cache_settings] Failed to persist clear: %s", exc)
        raise HTTPException(status_code=500, detail="Failed to clear cache")

    await audit_log_service.record_admin_event(
        db,
        action=audit_log_service.ACTION_CACHE_CLEARED,
        admin_user_id=getattr(current_user, "id", None),
        admin_email=getattr(current_user, "email", None),
        details={
            "cache_version": cfg["cache_version"],
            "reason": (payload or {}).get("reason") if isinstance(payload, dict) else None,
        },
        request=request,
    )

    return CacheConfig(**cfg)


@router.get("/public", response_model=CacheConfig)
async def get_cache_config_public(
    db: AsyncSession = Depends(get_db),
) -> CacheConfig:
    """Public read-only endpoint so the storefront can pick up cache-version
    changes without requiring admin auth.
    """
    row = await _get_or_create_row(db)
    cfg = _parse_config(row)
    return CacheConfig(**cfg)