"""
用户数据备份与恢复 API

备份范围（用户创建的数据）：
1. 用户登录信息（users 表）
2. 案件管理数据（cases, case_threads, case_logs, case_documents 等）
3. 电子取证清单（evidence_checklists, evidence_checklist_items）
4. 激活码数据（system_configs 中的 license_info）
5. 对话记录（chat_threads, chat_messages 等）

备份方式：直接复制 user.db 文件（包含所有用户表）+ license_info
"""
import os
import json
import shutil
import logging
import time
from datetime import datetime
from fastapi import APIRouter, Depends, HTTPException, UploadFile, File, Form, Request

from ...core.database import get_pool
from ...core.permission import get_current_user, is_admin_role

router = APIRouter()
logger = logging.getLogger(__name__)

# 路径
_PROJECT_ROOT = os.path.normpath(os.path.join(os.path.dirname(__file__), "..", "..", "..", ".."))
DATA_DIR = os.path.join(_PROJECT_ROOT, "data")
BACKUP_DIR = os.path.join(DATA_DIR, "backups", "user_data")


def _require_admin(request: Request):
    user = get_current_user(request)
    if not user or not is_admin_role(user.get('role', '')):
        raise HTTPException(status_code=403, detail="需要管理员权限")
    return user


def _get_version() -> str:
    try:
        with open(os.path.join(_PROJECT_ROOT, "VERSION"), "r", encoding="utf-8") as f:
            return f.read().strip()
    except Exception:
        return "unknown"


@router.post("/user-backup/create")
async def create_user_backup(request: Request):
    """
    创建用户数据备份，保存到本地 backup/ 目录。
    备份内容：user.db（含登录信息、案件、取证清单、对话记录等）+ license_info
    """
    _require_admin(request)

    # 备份目录：优先使用用户配置
    try:
        from .config_paths import get_backup_save_path
        backup_dir = await get_backup_save_path()
    except Exception:
        backup_dir = os.path.join(_PROJECT_ROOT, "backup")
        os.makedirs(backup_dir, exist_ok=True)

    # WAL checkpoint
    try:
        pool = await get_pool()
        await pool.checkpoint()
    except Exception as e:
        logger.warning(f"WAL checkpoint 失败: {e}")

    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    zip_name = f"user_backup_{timestamp}.zip"
    zip_path = os.path.join(backup_dir, zip_name)

    import zipfile
    try:
        with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zf:
            # 1. user.db（核心用户数据）
            user_db = os.path.join(DATA_DIR, "user.db")
            if os.path.exists(user_db):
                zf.write(user_db, "user.db")
            else:
                raise HTTPException(status_code=500, detail="user.db 不存在")

            # WAL/SHM（确保数据完整）
            for suffix in ["-wal", "-shm"]:
                p = user_db + suffix
                if os.path.exists(p):
                    zf.write(p, "user.db" + suffix)

            # 2. license_info（从 system_configs 读取）
            license_info = _read_license_info()
            zf.writestr("license_info.json", json.dumps(license_info, ensure_ascii=False, indent=2))

            # 3. manifest
            manifest = {
                "version": "1.0",
                "backup_type": "user_data",
                "created_at": datetime.now().isoformat(),
                "system_version": _get_version(),
                "contents": [
                    "user.db（登录信息、案件、对话、取证清单等）",
                    "license_info.json（激活码数据）",
                ],
            }
            zf.writestr("manifest.json", json.dumps(manifest, ensure_ascii=False, indent=2))

        size_mb = os.path.getsize(zip_path) / 1024 / 1024
        logger.info(f"用户备份已创建: {zip_path} ({size_mb:.1f} MB)")

        return {
            "success": True,
            "path": zip_path,
            "filename": zip_name,
            "size_mb": round(size_mb, 1),
            "message": f"备份已保存到: {zip_path}",
        }

    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"创建备份失败: {e}", exc_info=True)
        if os.path.exists(zip_path):
            os.remove(zip_path)
        raise HTTPException(status_code=500, detail=f"创建备份失败: {e}")


@router.get("/user-backup/list")
async def list_user_backups(request: Request):
    """列出所有用户备份文件"""
    _require_admin(request)

    try:
        from .config_paths import get_backup_save_path
        backup_dir = await get_backup_save_path()
    except Exception:
        backup_dir = os.path.join(_PROJECT_ROOT, "backup")
    if not os.path.isdir(backup_dir):
        return {"success": True, "data": [], "path": backup_dir}

    files = []
    for f in sorted(os.listdir(backup_dir), reverse=True):
        if f.endswith(".zip") and f.startswith("user_backup_"):
            fpath = os.path.join(backup_dir, f)
            stat = os.stat(fpath)
            files.append({
                "filename": f,
                "path": fpath,
                "size_mb": round(stat.st_size / 1024 / 1024, 1),
                "created_at": datetime.fromtimestamp(stat.st_mtime).strftime("%Y-%m-%d %H:%M:%S"),
            })

    return {"success": True, "data": files, "path": backup_dir}


@router.post("/user-backup/preview")
async def preview_user_backup(request: Request, file: UploadFile = File(...)):
    """预览备份 ZIP 内容"""
    _require_admin(request)

    if not file.filename.endswith(".zip"):
        raise HTTPException(status_code=400, detail="请上传 .zip 文件")

    import tempfile, zipfile
    tmp = tempfile.NamedTemporaryFile(suffix=".zip", delete=False)
    try:
        content = await file.read()
        tmp.write(content)
        tmp.close()

        with zipfile.ZipFile(tmp.name, "r") as zf:
            names = zf.namelist()
            has_user_db = "user.db" in names
            has_license = "license_info.json" in names
            has_manifest = "manifest.json" in names

            manifest = None
            if has_manifest:
                manifest = json.loads(zf.read("manifest.json"))

            total_size = sum(info.file_size for info in zf.infolist())

            return {
                "valid": has_user_db,
                "has_user_db": has_user_db,
                "has_license_info": has_license,
                "manifest": manifest,
                "total_size_mb": round(total_size / 1024 / 1024, 1),
                "files": names,
            }
    except zipfile.BadZipFile:
        raise HTTPException(status_code=400, detail="无效的 ZIP 文件")
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"预览失败: {e}")
    finally:
        os.unlink(tmp.name)


@router.post("/user-backup/restore")
async def restore_user_backup(request: Request, file: UploadFile = File(...)):
    """
    从备份恢复用户数据。
    恢复前自动备份当前 user.db，恢复后需重启程序。
    """
    _require_admin(request)

    if not file.filename.endswith(".zip"):
        raise HTTPException(status_code=400, detail="请上传 .zip 文件")

    import tempfile, zipfile
    tmp = tempfile.NamedTemporaryFile(suffix=".zip", delete=False)
    try:
        content = await file.read()
        tmp.write(content)
        tmp.close()

        # 验证 ZIP
        with zipfile.ZipFile(tmp.name, "r") as zf:
            names = zf.namelist()
            if "user.db" not in names:
                raise HTTPException(status_code=400, detail="备份文件缺少 user.db，无法恢复")

            # 读取 manifest
            manifest = None
            if "manifest.json" in names:
                manifest = json.loads(zf.read("manifest.json"))

        # 1. WAL checkpoint
        try:
            pool = await get_pool()
            await pool.checkpoint()
        except Exception as e:
            logger.warning(f"WAL checkpoint 失败: {e}")

        # 2. 备份当前 user.db（恢复前安全措施）
        user_db = os.path.join(DATA_DIR, "user.db")
        pre_backup_dir = os.path.join(BACKUP_DIR, f"pre_restore_{datetime.now().strftime('%Y%m%d_%H%M%S')}")
        os.makedirs(pre_backup_dir, exist_ok=True)
        if os.path.exists(user_db):
            shutil.copy2(user_db, os.path.join(pre_backup_dir, "user.db"))
            logger.info(f"恢复前备份: {pre_backup_dir}/user.db")

        # 3. 解压恢复
        restored = []
        with zipfile.ZipFile(tmp.name, "r") as zf:
            # 恢复 user.db
            target = os.path.join(DATA_DIR, "user.db")
            with zf.open("user.db") as src, open(target, "wb") as dst:
                shutil.copyfileobj(src, dst)
            restored.append("user.db")

            # 恢复 license_info
            if "license_info.json" in zf.namelist():
                license_info = json.loads(zf.read("license_info.json"))
                _write_license_info(license_info)
                restored.append("license_info（激活码数据）")

        logger.info(f"用户数据恢复完成: {restored}")

        return {
            "success": True,
            "restored": restored,
            "pre_backup_path": pre_backup_dir,
            "manifest": manifest,
            "message": f"恢复成功！已恢复：{', '.join(restored)}。请重启程序使更改生效。",
        }

    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"恢复失败: {e}", exc_info=True)
        raise HTTPException(status_code=500, detail=f"恢复失败: {e}")
    finally:
        os.unlink(tmp.name)


def _read_license_info() -> dict:
    """从 runtime.db 读取 license_info（敏感数据存储在 runtime.db，非 system.db）"""
    import sqlite3
    runtime_db = os.path.join(DATA_DIR, "runtime.db")
    result = {}
    if not os.path.exists(runtime_db):
        return result
    try:
        conn = sqlite3.connect(runtime_db)
        cur = conn.cursor()
        cur.execute("SELECT config_value FROM system_configs WHERE config_key = 'license_info'")
        row = cur.fetchone()
        if row and row[0]:
            result = json.loads(row[0])
        conn.close()
    except Exception as e:
        logger.warning(f"读取 license_info 失败: {e}")
    return result


def _write_license_info(license_info: dict):
    """将 license_info 写入 runtime.db（敏感数据存储在 runtime.db，非 system.db）"""
    import sqlite3
    runtime_db = os.path.join(DATA_DIR, "runtime.db")
    if not os.path.exists(runtime_db):
        return
    try:
        conn = sqlite3.connect(runtime_db)
        cur = conn.cursor()
        value = json.dumps(license_info, ensure_ascii=False)
        cur.execute(
            "INSERT OR REPLACE INTO system_configs (config_key, config_value) VALUES (?, ?)",
            ("license_info", value),
        )
        conn.commit()
        conn.close()
        logger.info("license_info 已恢复到 runtime.db")
    except Exception as e:
        logger.warning(f"写入 license_info 失败: {e}")
