"""System routes: backup, restore, auto-backup.

Backups are ZIP files containing a SQLite database snapshot (.db)
and any uploaded asset files from data/assets/.
"""

import asyncio
import io
import subprocess
import sys
import threading
import zipfile
from pathlib import Path

from fastapi import APIRouter, Depends, File, HTTPException, UploadFile
from fastapi.responses import StreamingResponse

from wled_controller.api.auth import AuthRequired
from wled_controller.api.dependencies import get_asset_store, get_auto_backup_engine, get_database
from wled_controller.api.schemas.system import (
    AutoBackupSettings,
    AutoBackupStatusResponse,
    BackupFileInfo,
    BackupListResponse,
    RestoreResponse,
)
from wled_controller.config import get_config
from wled_controller.core.backup.auto_backup import AutoBackupEngine
from wled_controller.storage.asset_store import AssetStore
from wled_controller.storage.database import Database, freeze_writes
from wled_controller.utils import get_logger

logger = get_logger(__name__)

router = APIRouter()

_SERVER_DIR = Path(__file__).resolve().parents[4]


def _schedule_restart() -> None:
    """Spawn a restart script after a short delay so the HTTP response completes."""

    def _restart():
        import time
        time.sleep(1)
        if sys.platform == "win32":
            subprocess.Popen(
                ["powershell", "-ExecutionPolicy", "Bypass", "-File",
                 str(_SERVER_DIR / "restart.ps1")],
                creationflags=subprocess.DETACHED_PROCESS | subprocess.CREATE_NEW_PROCESS_GROUP,
            )
        else:
            subprocess.Popen(
                ["bash", str(_SERVER_DIR / "restart.sh")],
                start_new_session=True,
            )

    threading.Thread(target=_restart, daemon=True).start()


# ---------------------------------------------------------------------------
# Backup / restore (SQLite snapshots)
# ---------------------------------------------------------------------------


@router.get("/api/v1/system/backup", tags=["System"])
def backup_config(
    _: AuthRequired,
    db: Database = Depends(get_database),
    asset_store: AssetStore = Depends(get_asset_store),
):
    """Download a full backup as a .zip containing the database and asset files."""
    import tempfile
    with tempfile.NamedTemporaryFile(suffix=".db", delete=False) as tmp:
        tmp_path = Path(tmp.name)

    try:
        db.backup_to(tmp_path)
        db_content = tmp_path.read_bytes()
    finally:
        tmp_path.unlink(missing_ok=True)

    # Build ZIP: database + asset files
    zip_buffer = io.BytesIO()
    with zipfile.ZipFile(zip_buffer, "w", zipfile.ZIP_DEFLATED) as zf:
        zf.writestr("ledgrab.db", db_content)

        # Include all asset files
        assets_dir = Path(get_config().assets.assets_dir)
        if assets_dir.exists():
            for asset_file in assets_dir.iterdir():
                if asset_file.is_file():
                    zf.write(asset_file, f"assets/{asset_file.name}")

    zip_buffer.seek(0)

    from datetime import datetime, timezone
    timestamp = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H%M%S")
    filename = f"ledgrab-backup-{timestamp}.zip"

    return StreamingResponse(
        zip_buffer,
        media_type="application/zip",
        headers={"Content-Disposition": f'attachment; filename="{filename}"'},
    )


@router.post("/api/v1/system/restore", response_model=RestoreResponse, tags=["System"])
async def restore_config(
    _: AuthRequired,
    file: UploadFile = File(...),
    db: Database = Depends(get_database),
):
    """Upload a .db or .zip backup file to restore all configuration. Triggers server restart.

    ZIP backups contain the database and asset files. Plain .db backups are
    also supported for backward compatibility (assets are not restored).
    """
    raw = await file.read()
    if len(raw) > 200 * 1024 * 1024:  # 200 MB limit (ZIP may contain assets)
        raise HTTPException(status_code=400, detail="Backup file too large (max 200 MB)")

    if len(raw) < 100:
        raise HTTPException(status_code=400, detail="File too small to be a valid backup")

    import tempfile

    is_zip = raw[:4] == b"PK\x03\x04"
    is_sqlite = raw[:16].startswith(b"SQLite format 3")

    if not is_zip and not is_sqlite:
        raise HTTPException(status_code=400, detail="Not a valid backup file (expected .zip or .db)")

    if is_zip:
        # Extract DB and assets from ZIP
        try:
            with zipfile.ZipFile(io.BytesIO(raw)) as zf:
                names = zf.namelist()
                if "ledgrab.db" not in names:
                    raise HTTPException(status_code=400, detail="ZIP backup missing ledgrab.db")

                db_bytes = zf.read("ledgrab.db")

                # Restore asset files
                assets_dir = Path(get_config().assets.assets_dir)
                assets_dir.mkdir(parents=True, exist_ok=True)
                for name in names:
                    if name.startswith("assets/") and not name.endswith("/"):
                        asset_filename = name.split("/", 1)[1]
                        dest = assets_dir / asset_filename
                        dest.write_bytes(zf.read(name))
                        logger.info(f"Restored asset file: {asset_filename}")
        except zipfile.BadZipFile:
            raise HTTPException(status_code=400, detail="Invalid ZIP file")
    else:
        db_bytes = raw

    with tempfile.NamedTemporaryFile(suffix=".db", delete=False) as tmp:
        tmp.write(db_bytes)
        tmp_path = Path(tmp.name)

    try:
        def _restore():
            db.restore_from(tmp_path)

        await asyncio.to_thread(_restore)
    finally:
        tmp_path.unlink(missing_ok=True)

    freeze_writes()
    logger.info("Database restored from uploaded backup. Scheduling restart...")
    _schedule_restart()

    return RestoreResponse(
        status="restored",
        restart_scheduled=True,
        message="Database restored from backup. Server restarting...",
    )


@router.post("/api/v1/system/restart", tags=["System"])
def restart_server(_: AuthRequired):
    """Schedule a server restart and return immediately."""
    from wled_controller.server_ref import _broadcast_restarting
    _broadcast_restarting()
    _schedule_restart()
    return {"status": "restarting"}


@router.post("/api/v1/system/shutdown", tags=["System"])
def shutdown_server(_: AuthRequired):
    """Gracefully shut down the server."""
    from wled_controller.server_ref import request_shutdown
    request_shutdown()
    return {"status": "shutting_down"}


# ---------------------------------------------------------------------------
# Auto-backup settings & saved backups
# ---------------------------------------------------------------------------


@router.get(
    "/api/v1/system/auto-backup/settings",
    response_model=AutoBackupStatusResponse,
    tags=["System"],
)
async def get_auto_backup_settings(
    _: AuthRequired,
    engine: AutoBackupEngine = Depends(get_auto_backup_engine),
):
    """Get auto-backup settings and status."""
    return engine.get_settings()


@router.put(
    "/api/v1/system/auto-backup/settings",
    response_model=AutoBackupStatusResponse,
    tags=["System"],
)
async def update_auto_backup_settings(
    _: AuthRequired,
    body: AutoBackupSettings,
    engine: AutoBackupEngine = Depends(get_auto_backup_engine),
):
    """Update auto-backup settings (enable/disable, interval, max backups)."""
    return await engine.update_settings(
        enabled=body.enabled,
        interval_hours=body.interval_hours,
        max_backups=body.max_backups,
    )


@router.post("/api/v1/system/auto-backup/trigger", tags=["System"])
async def trigger_backup(
    _: AuthRequired,
    engine: AutoBackupEngine = Depends(get_auto_backup_engine),
):
    """Manually trigger a backup now."""
    backup = await engine.trigger_backup()
    return {"status": "ok", "backup": backup}


@router.get(
    "/api/v1/system/backups",
    response_model=BackupListResponse,
    tags=["System"],
)
async def list_backups(
    _: AuthRequired,
    engine: AutoBackupEngine = Depends(get_auto_backup_engine),
):
    """List all saved backup files."""
    backups = engine.list_backups()
    return BackupListResponse(
        backups=[BackupFileInfo(**b) for b in backups],
        count=len(backups),
    )


@router.get("/api/v1/system/backups/{filename}", tags=["System"])
def download_saved_backup(
    filename: str,
    _: AuthRequired,
    engine: AutoBackupEngine = Depends(get_auto_backup_engine),
):
    """Download a specific saved backup file."""
    try:
        path = engine.get_backup_path(filename)
    except (ValueError, FileNotFoundError) as e:
        raise HTTPException(status_code=404, detail=str(e))

    content = path.read_bytes()
    return StreamingResponse(
        io.BytesIO(content),
        media_type="application/octet-stream",
        headers={"Content-Disposition": f'attachment; filename="{filename}"'},
    )


@router.delete("/api/v1/system/backups/{filename}", tags=["System"])
async def delete_saved_backup(
    filename: str,
    _: AuthRequired,
    engine: AutoBackupEngine = Depends(get_auto_backup_engine),
):
    """Delete a specific saved backup file."""
    try:
        engine.delete_backup(filename)
    except (ValueError, FileNotFoundError) as e:
        raise HTTPException(status_code=404, detail=str(e))
    return {"status": "deleted", "filename": filename}
