"""
Settings, health, and utility endpoints for SLTK API.

Handles user settings, file uploads, server logs, health checks, and system info.
"""

from __future__ import annotations

import json
import logging
import os
import shutil
import stat
import threading
import uuid
from datetime import datetime
from pathlib import Path
from typing import Any

from fastapi import APIRouter, HTTPException, Query, Request, UploadFile
from fastapi import File as FastAPIFile

from sltk.api.dependencies import rate_limit, safe_error_detail
from sltk.api.models import UserSettings, WeightStatus

router = APIRouter(tags=["settings"])
logger = logging.getLogger(__name__)

# ============================================================================
# Settings Storage
# ============================================================================

# User settings directory
SETTINGS_DIR = Path.home() / ".sltk"
SETTINGS_FILE = SETTINGS_DIR / "settings.json"

# Upload directory - configurable via environment variable
UPLOAD_DIR = Path(os.environ.get("SLTK_UPLOAD_DIR", "/tmp/sltk_uploads"))
UPLOAD_DIR.mkdir(parents=True, exist_ok=True)

# ============================================================================
# Server Logs Storage
# ============================================================================

# In-memory server logs storage
_server_logs: list[dict[str, Any]] = []
_server_logs_lock = threading.Lock()
_max_server_logs = 500


def add_server_log(level: str, message: str, source: str = "api"):
    """Add a log entry to the server logs. Thread-safe."""
    global _server_logs
    entry = {
        "timestamp": datetime.now().isoformat(),
        "level": level.upper(),
        "message": message,
        "source": source,
    }
    with _server_logs_lock:
        _server_logs.append(entry)
        # Trim if too many logs
        if len(_server_logs) > _max_server_logs:
            _server_logs = _server_logs[-_max_server_logs:]


# ============================================================================
# User Settings Endpoints
# ============================================================================


def _mask_api_key(key: str | None) -> str | None:
    """Mask an API key for safe display: sk-abc...wxyz."""
    if not key or len(key) < 12:
        return None
    return key[:7] + "..." + key[-4:]


@router.get("/api/settings")
async def get_user_settings() -> UserSettings:
    """Get user settings from ~/.sltk/settings.json.

    API keys are masked in the response to prevent leakage.
    """
    try:
        if SETTINGS_FILE.exists():
            with open(SETTINGS_FILE) as f:
                data = json.load(f)
                settings = UserSettings(**data)
                # Mask API keys before returning
                settings.openai_api_key = _mask_api_key(settings.openai_api_key)
                settings.anthropic_api_key = _mask_api_key(settings.anthropic_api_key)
                return settings
    except Exception as e:
        add_server_log("WARNING", f"Failed to load settings: {e}", "settings")

    return UserSettings()


@router.post("/api/settings")
async def save_user_settings(settings: UserSettings) -> dict[str, str]:
    """Save user settings to ~/.sltk/settings.json.

    If openai_api_key looks like a masked value (contains '...'),
    the existing key on disk is preserved instead of being overwritten.
    """
    try:
        SETTINGS_DIR.mkdir(parents=True, exist_ok=True)
        data = settings.model_dump()

        # Preserve existing API keys if the incoming values are masked
        existing_data = {}
        if SETTINGS_FILE.exists():
            existing_data = json.load(open(SETTINGS_FILE))

        for key_field in ("openai_api_key", "anthropic_api_key"):
            incoming_key = data.get(key_field)
            if incoming_key and "..." in incoming_key:
                data[key_field] = existing_data.get(key_field)

        with open(SETTINGS_FILE, "w") as f:
            json.dump(data, f, indent=2)

        # Restrict file permissions if it contains an API key (owner rw only)
        if data.get("openai_api_key") or data.get("anthropic_api_key"):
            os.chmod(SETTINGS_FILE, stat.S_IRUSR | stat.S_IWUSR)

        add_server_log("INFO", "Settings saved successfully", "settings")
        return {"status": "saved", "path": str(SETTINGS_FILE)}
    except Exception as e:
        add_server_log("ERROR", f"Failed to save settings: {e}", "settings")
        raise HTTPException(500, safe_error_detail("Failed to save settings", e))


# ============================================================================
# Server Logs Endpoint
# ============================================================================


@router.get("/api/logs")
async def get_server_logs(limit: int = Query(100, ge=1, le=500)):
    """Get recent server logs."""
    with _server_logs_lock:
        logs = _server_logs[-limit:] if limit < len(_server_logs) else _server_logs.copy()
        total = len(_server_logs)
    return {
        "logs": logs,
        "total": total,
    }


# ============================================================================
# File Upload Endpoints
# ============================================================================


@router.post("/api/upload")
async def upload_file(file: UploadFile = FastAPIFile(...)):
    """
    Upload a video or ELAN file.

    Returns the server path where the file is saved.
    """
    if not file.filename:
        raise HTTPException(400, "No filename provided")

    # Validate file extension
    ext = Path(file.filename).suffix.lower()
    video_exts = {".mp4", ".mov", ".avi", ".mkv", ".webm"}
    elan_exts = {".eaf", ".pfsx"}

    if ext not in video_exts and ext not in elan_exts:
        raise HTTPException(400, f"Unsupported file type: {ext}. Allowed: {video_exts | elan_exts}")

    file_type = "video" if ext in video_exts else "elan"

    # Generate unique filename to avoid collisions
    unique_id = str(uuid.uuid4())[:8]
    safe_filename = f"{unique_id}_{Path(file.filename).name}"
    save_path = UPLOAD_DIR / safe_filename

    # Save the file
    try:
        with save_path.open("wb") as buffer:
            shutil.copyfileobj(file.file, buffer)

        add_server_log("INFO", f"File uploaded: {safe_filename} ({file_type})", "upload")

        return {
            "path": str(save_path),
            "filename": safe_filename,
            "original_name": file.filename,
            "size": save_path.stat().st_size,
            "type": file_type,
        }
    except Exception as e:
        add_server_log("ERROR", f"Upload failed: {e}", "upload")
        raise HTTPException(500, safe_error_detail("Failed to save file", e))


@router.get("/api/uploads")
async def list_uploads():
    """List all uploaded files."""
    uploads = []
    for f in UPLOAD_DIR.iterdir():
        if f.is_file():
            ext = f.suffix.lower()
            video_exts = {".mp4", ".mov", ".avi", ".mkv", ".webm"}
            elan_exts = {".eaf", ".pfsx"}
            file_type = "video" if ext in video_exts else "elan" if ext in elan_exts else "other"
            uploads.append(
                {
                    "path": str(f),
                    "filename": f.name,
                    "size": f.stat().st_size,
                    "type": file_type,
                    "modified": datetime.fromtimestamp(f.stat().st_mtime).isoformat(),
                }
            )
    return {
        "uploads": sorted(uploads, key=lambda x: x["modified"], reverse=True),
        "total": len(uploads),
        "upload_dir": str(UPLOAD_DIR),
    }


@router.delete("/api/uploads/{filename}")
async def delete_upload(filename: str):
    """Delete an uploaded file."""
    # Security: only allow deleting from upload dir
    file_path = UPLOAD_DIR / filename
    if not file_path.exists():
        raise HTTPException(404, "File not found")
    if not file_path.is_relative_to(UPLOAD_DIR):
        raise HTTPException(403, "Access denied")

    try:
        file_path.unlink()
        add_server_log("INFO", f"File deleted: {filename}", "upload")
        return {"status": "deleted", "filename": filename}
    except Exception as e:
        raise HTTPException(500, safe_error_detail("Failed to delete file", e))


# ============================================================================
# System Endpoints
# ============================================================================


@router.get("/api/system/weights")
async def get_weights_status() -> dict[str, WeightStatus]:
    """
    Get status of all bundled model weights.

    Returns status for each known weight file including path,
    existence, size, and source (bundled vs environment override).
    """
    from sltk.extraction.weights import get_all_weights_status

    return get_all_weights_status()  # type: ignore[return-value]


# ============================================================================
# Health and Info Endpoints
# ============================================================================


@router.get("/api/health")
@rate_limit("1000/minute")
async def health_check(request: Request):
    """Health check endpoint. Rate limited to 1000 requests per minute."""
    return {"status": "healthy", "timestamp": datetime.now().isoformat()}


@router.get("/api/info")
async def get_api_info():
    """Get API information."""
    import sltk

    # Check CUDA availability
    cuda_available = False
    cuda_device = None
    try:
        import torch

        cuda_available = torch.cuda.is_available()
        if cuda_available:
            cuda_device = torch.cuda.get_device_name(0)
    except ImportError:
        pass

    return {
        "name": "SLTK API",
        "version": "0.1.0",
        "sltk_version": getattr(sltk, "__version__", "unknown"),
        "cuda_available": cuda_available,
        "cuda_device": cuda_device,
        "endpoints": {
            "video_discovery": "/api/videos/discover",
            "extraction": "/api/extraction/start",
            "poses": "/api/poses/load",
            "segments": "/api/segments/load",
            "datasets": "/api/datasets/list",
        },
    }
