/**
 * 漫剧生产线 - 桌面版存储服务
 * 适配 Tauri 桌面应用：自带静态文件服务 + API代理
 * 运行: node drama-server-desktop.js
 * 端口: 3002
 */

const http = require('http');
const fs = require('fs');
const path = require('path');
const https = require('https');
const crypto = require('crypto');
const { execSync } = require('child_process');

// 数据目录 - 使用应用同级的 data/ 目录
const APP_DIR = path.resolve(__dirname, '..');
const DATA_DIR = path.join(APP_DIR, 'data', 'drama-projects');
const USER_DIR = path.join(APP_DIR, 'data', 'drama-users');
const USER_FILE = path.join(USER_DIR, 'users.json');
const BACKUP_DIR = path.join(APP_DIR, 'data', 'drama-backups', 'snapshots');
const PUBLIC_DIR = path.join(APP_DIR, 'src');

// 确保目录存在
[DATA_DIR, USER_DIR, BACKUP_DIR, path.join(APP_DIR, 'data', 'drama-backups', 'daily'), path.join(APP_DIR, 'data', 'drama-backups', 'manual')].forEach(dir => {
  if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
});
if (!fs.existsSync(USER_FILE)) fs.writeFileSync(USER_FILE, JSON.stringify({ users: [] }));

// bltcy.ai API配置
const BLTCY_KEY = process.env.BLTCY_API_KEY || 'sk-vyGozxB8srorWxB8Vk6Am43UEGkgDqVRuthScYwu2ci5SSs4';
const BLTCY_HOST = 'api.bltcy.ai';

// ====== MIME类型 ======
const MIME = {
  '.html': 'text/html; charset=utf-8',
  '.css': 'text/css; charset=utf-8',
  '.js': 'application/javascript; charset=utf-8',
  '.json': 'application/json',
  '.png': 'image/png',
  '.jpg': 'image/jpeg',
  '.jpeg': 'image/jpeg',
  '.gif': 'image/gif',
  '.svg': 'image/svg+xml',
  '.ico': 'image/x-icon',
  '.tar.gz': 'application/gzip',
  '.gz': 'application/gzip',
  '.webp': 'image/webp',
};

// ====== 辅助函数 ======
function json(res, status, data) {
  res.writeHead(status, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Headers': 'Content-Type, Authorization', 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS' });
  res.end(JSON.stringify(data));
}
function readBody(req) {
  return new Promise((resolve, reject) => {
    let b = '';
    req.on('data', c => b += c);
    req.on('end', () => { try { resolve(JSON.parse(b)); } catch(e) { resolve({}); } });
    req.on('error', reject);
  });
}
function loadUsers() {
  try { return JSON.parse(fs.readFileSync(USER_FILE, 'utf-8')); } catch(e) { return { users: [] }; }
}
function saveUsers(data) {
  fs.writeFileSync(USER_FILE, JSON.stringify(data, null, 2), 'utf-8');
}

// ====== 静态文件服务 ======
function serveStatic(req, res) {
  let urlPath = new URL(req.url, 'http://localhost').pathname;
  if (urlPath === '/' || urlPath === '/index.html') urlPath = '/index.html';
  const filePath = path.join(PUBLIC_DIR, urlPath);
  
  // 安全检查
  if (!filePath.startsWith(PUBLIC_DIR)) {
    res.writeHead(403); res.end('Forbidden');
    return true;
  }
  
  if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) {
    const ext = path.extname(filePath);
    res.writeHead(200, { 'Content-Type': MIME[ext] || 'application/octet-stream', 'Cache-Control': 'no-cache' });
    fs.createReadStream(filePath).pipe(res);
    return true;
  }
  return false;
}

// ====== bltcy.ai API代理 ======
function proxyBltcy(req, res) {
  const pathname = new URL(req.url, 'http://localhost').pathname;
  if (!pathname.startsWith('/bltcy-api/')) return false;
  
  const targetPath = pathname.replace('/bltcy-api/', '');
  const options = {
    hostname: BLTCY_HOST,
    port: 443,
    path: '/v1/' + targetPath,
    method: req.method,
    headers: {
      'Authorization': 'Bearer ' + BLTCY_KEY,
      'Content-Type': 'application/json',
      'Host': BLTCY_HOST
    }
  };
  
  const proxyReq = https.request(options, proxyRes => {
    let data = '';
    proxyRes.on('data', c => data += c);
    proxyRes.on('end', () => {
      res.writeHead(proxyRes.statusCode, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
      res.end(data);
    });
  });
  proxyReq.on('error', e => { json(res, 502, { error: 'Proxy error: ' + e.message }); });
  
  let body = '';
  req.on('data', c => body += c);
  req.on('end', () => { proxyReq.end(body); });
  return true;
}

// ====== 服务端核心逻辑（移植自 drama-server.js） ======
const server = http.createServer(async (req, res) => {
  const method = req.method;
  const url = new URL(req.url, 'http://localhost');
  const pathname = url.pathname;
  
  // CORS
  if (method === 'OPTIONS') {
    res.writeHead(204, { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Headers': 'Content-Type, Authorization', 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS' });
    res.end();
    return;
  }
  
  // bltcy API代理
  if (proxyBltcy(req, res)) return;
  
  // 静态文件
  if (method === 'GET' && (pathname === '/' || pathname === '/index.html' || pathname === '/drama.css')) {
    if (serveStatic(req, res)) return;
  }
  
  // ====== API路由（移植自 drama-server.js 的核心逻辑） ======
  
  // GET /api/projects - 项目列表
  if (method === 'GET' && pathname === '/api/projects') {
    try {
      const files = fs.readdirSync(DATA_DIR).filter(f => f.endsWith('.json'));
      const projects = files.map(f => {
        const filePath = path.join(DATA_DIR, f);
        try {
          const data = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
          return { id: data.id, name: data.name, currentStep: data.currentStep, updatedAt: fs.statSync(filePath).mtime.toISOString() };
        } catch(e) { return null; }
      }).filter(Boolean).sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
      json(res, 200, { projects });
    } catch(e) { json(res, 500, { error: e.message }); }
    return;
  }
  
  // GET /api/project/:id - 加载项目
  const projectMatch = pathname.match(/^\/api\/project\/(.+)$/);
  if (method === 'GET' && projectMatch) {
    const id = projectMatch[1];
    const filePath = path.join(DATA_DIR, `${id}.json`);
    if (!fs.existsSync(filePath)) { json(res, 404, { error: 'Project not found' }); return; }
    try {
      const data = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
      json(res, 200, { success: true, project: data });
    } catch(e) { json(res, 500, { error: e.message }); }
    return;
  }
  
  // PUT /api/project/:id - 保存项目
  if (method === 'PUT' && projectMatch) {
    const id = projectMatch[1];
    const body = await readBody(req);
    const filePath = path.join(DATA_DIR, `${id}.json`);
    try {
      fs.writeFileSync(filePath, JSON.stringify(body, null, 2), 'utf-8');
      json(res, 200, { success: true });
    } catch(e) { json(res, 500, { error: e.message }); }
    return;
  }
  
  // DELETE /api/project/:id
  if (method === 'DELETE' && projectMatch) {
    const id = projectMatch[1];
    const filePath = path.join(DATA_DIR, `${id}.json`);
    try {
      if (fs.existsSync(filePath)) fs.unlinkSync(filePath);
      json(res, 200, { success: true });
    } catch(e) { json(res, 500, { error: e.message }); }
    return;
  }
  
  // ====== Auth routes ======
  
  // POST /api/auth/login
  if (method === 'POST' && pathname === '/api/auth/login') {
    const body = await readBody(req);
    const { username, password } = body || {};
    if (!username || !password) { json(res, 400, { error: '请输入用户名和密码' }); return; }
    const users = loadUsers();
    const user = users.users.find(u => u.username === username);
    if (!user) { json(res, 401, { error: '用户名或密码错误' }); return; }
    const hash = crypto.scryptSync(password, user.salt, 32).toString('hex');
    if (hash !== user.passwordHash) { json(res, 401, { error: '用户名或密码错误' }); return; }
    if (user.status === 'pending') { json(res, 403, { error: '账号正在等待管理员审核' }); return; }
    if (user.status === 'rejected') { json(res, 403, { error: '账号审核未通过' }); return; }
    // Trial start
    if (user.membership === 'trial' && !user.trialStartAt) {
      user.trialStartAt = new Date().toISOString();
      user.trialEndAt = new Date(Date.now() + 7 * 86400000).toISOString();
      user.expireAt = user.trialEndAt;
    }
    // Expiry check
    if (user.expireAt && new Date(user.expireAt) < new Date()) {
      json(res, 403, { error: user.membership === 'trial' ? '7天试用已结束，请联系管理员续费' : '会员已到期，请联系管理员续费' }); return;
    }
    const token = crypto.randomUUID();
    if (!user.tokens) user.tokens = [];
    user.tokens.push(token);
    saveUsers(users);
    json(res, 200, { success: true, token, user: { id: user.id, username: user.username, phone: user.phone||'', isAdmin: !!user.isAdmin, membership: user.membership, membershipEndAt: user.expireAt } });
    return;
  }
  
  // POST /api/auth/register
  if (method === 'POST' && pathname === '/api/auth/register') {
    const body = await readBody(req);
    const { username, password, phone } = body || {};
    if (!username || username.length < 2) { json(res, 400, { error: '用户名至少2个字符' }); return; }
    if (!password || password.length < 4) { json(res, 400, { error: '密码至少4个字符' }); return; }
    if (!phone || !/^1\d{10}$/.test(phone)) { json(res, 400, { error: '请输入正确的11位手机号码' }); return; }
    const users = loadUsers();
    if (users.users.find(u => u.username === username)) { json(res, 409, { error: '用户名已存在' }); return; }
    if (users.users.find(u => u.phone === phone)) { json(res, 409, { error: '该手机号已注册' }); return; }
    const salt = crypto.randomUUID();
    const passwordHash = crypto.scryptSync(password, salt, 32).toString('hex');
    const token = crypto.randomUUID();
    users.users.push({ id: 'u_' + crypto.randomUUID().substring(0, 8), username, phone, passwordHash, salt, tokens: [token], status: 'pending', membership: 'trial', trialStartAt: null, trialEndAt: null, expireAt: null, createdAt: new Date().toISOString(), isAdmin: false, settings: {} });
    saveUsers(users);
    json(res, 200, { success: true, message: '注册成功，请等待管理员审核' });
    return;
  }
  
  // POST /api/auth/rename
  if (method === 'POST' && pathname === '/api/auth/rename') {
    const body = await readBody(req);
    const auth = req.headers['authorization'];
    if (!auth) { json(res, 401, { error: '未登录' }); return; }
    const token = auth.replace('Bearer ', '');
    const users = loadUsers();
    const user = users.users.find(u => u.tokens && u.tokens.includes(token));
    if (!user) { json(res, 401, { error: '未登录' }); return; }
    const { newName } = body;
    if (!newName || newName.trim().length < 2) { json(res, 400, { error: '用户名至少2个字符' }); return; }
    if (users.users.some(u => u.username === newName.trim() && u.id !== user.id)) { json(res, 409, { error: '用户名已被占用' }); return; }
    user.username = newName.trim();
    saveUsers(users);
    json(res, 200, { success: true, username: newName.trim() });
    return;
  }
  
  // POST /api/auth/bind-phone
  if (method === 'POST' && pathname === '/api/auth/bind-phone') {
    const auth = req.headers['authorization'];
    if (!auth) { json(res, 401, { error: '未登录' }); return; }
    const token = auth.replace('Bearer ', '');
    const users = loadUsers();
    const user = users.users.find(u => u.tokens && u.tokens.includes(token));
    if (!user) { json(res, 401, { error: '未登录' }); return; }
    const body = await readBody(req);
    const { phone } = body;
    if (!phone || !/^1\d{10}$/.test(phone.trim())) { json(res, 400, { error: '手机号格式不正确' }); return; }
    if (users.users.some(u => u.phone === phone.trim() && u.id !== user.id)) { json(res, 409, { error: '手机号已被其他账号绑定' }); return; }
    user.phone = phone.trim();
    saveUsers(users);
    json(res, 200, { success: true, phone: phone.trim() });
    return;
  }
  
  // GET /api/auth/me
  if (method === 'GET' && pathname === '/api/auth/me') {
    const auth = req.headers['authorization'];
    if (!auth) { json(res, 401, { error: '未登录' }); return; }
    const token = auth.replace('Bearer ', '');
    const users = loadUsers();
    const user = users.users.find(u => u.tokens && u.tokens.includes(token));
    if (!user) { json(res, 401, { error: 'Token无效' }); return; }
    json(res, 200, { success: true, user: { id: user.id, username: user.username, phone: user.phone||'', isAdmin: !!user.isAdmin, membership: user.membership, membershipEndAt: user.expireAt } });
    return;
  }
  
  // GET /api/auth/users - 管理员用户列表
  if (method === 'GET' && pathname === '/api/auth/users') {
    const auth = req.headers['authorization'];
    if (!auth) { json(res, 401, { error: '未登录' }); return; }
    const token = auth.replace('Bearer ', '');
    const users = loadUsers();
    const me = users.users.find(u => u.tokens && u.tokens.includes(token));
    if (!me || !me.isAdmin) { json(res, 403, { error: '权限不足' }); return; }
    const list = users.users.map(u => ({ id: u.id, username: u.username, phone: u.phone||'', isAdmin: !!u.isAdmin, membership: u.membership, membershipEndAt: u.expireAt, status: u.status, createdAt: u.createdAt }));
    json(res, 200, { users: list });
    return;
  }
  
  // POST /api/auth/set-membership - 管理员调整会员等级
  if (method === 'POST' && pathname === '/api/auth/set-membership') {
    const auth = req.headers['authorization'];
    if (!auth) { json(res, 401, { error: '未登录' }); return; }
    const token = auth.replace('Bearer ', '');
    const users = loadUsers();
    const me = users.users.find(u => u.tokens && u.tokens.includes(token));
    if (!me || !me.isAdmin) { json(res, 403, { error: '权限不足' }); return; }
    const body = await readBody(req);
    const { targetId, plan } = body || {};
    const target = users.users.find(u => u.id === targetId);
    if (!target) { json(res, 404, { error: '用户不存在' }); return; }
    if (target.isAdmin) { json(res, 400, { error: '不能操作管理员账号' }); return; }
    target.membership = plan;
    if (plan === 'trial') {
      target.trialStartAt = null; target.membershipEndAt = null;
    } else {
      const durations = { vip_monthly: 30, vip_yearly: 365, enterprise: 365 * 3 };
      const days = durations[plan] || 30;
      target.expireAt = new Date(Date.now() + days * 86400000).toISOString();
    }
    saveUsers(users);
    json(res, 200, { success: true, targetId, membership: target.membership });
    return;
  }
  
  // POST /api/auth/set-admin
  if (method === 'POST' && pathname === '/api/auth/set-admin') {
    const auth = req.headers['authorization'];
    if (!auth) { json(res, 401, { error: '未登录' }); return; }
    const token = auth.replace('Bearer ', '');
    const users = loadUsers();
    const me = users.users.find(u => u.tokens && u.tokens.includes(token));
    if (!me || !me.isAdmin) { json(res, 403, { error: '权限不足' }); return; }
    const body = await readBody(req);
    const { targetId, isAdmin } = body || {};
    const target = users.users.find(u => u.id === targetId);
    if (!target) { json(res, 404, { error: '用户不存在' }); return; }
    target.isAdmin = !!isAdmin;
    saveUsers(users);
    json(res, 200, { success: true, targetId, isAdmin: target.isAdmin });
    return;
  }
  
  // ====== Backup routes ======
  
  // POST /api/create-snapshot
  if (method === 'POST' && pathname === '/api/create-snapshot') {
    try {
      const files = fs.readdirSync(DATA_DIR).filter(f => f.endsWith('.json'));
      files.forEach(f => {
        const src = path.join(DATA_DIR, f);
        const ts = new Date().toISOString().replace(/[:.]/g, '-');
        const projectId = f.replace('.json', '');
        const dst = path.join(BACKUP_DIR, projectId + '_snapshot_' + ts + '.json');
        fs.copyFileSync(src, dst);
      });
      const allSnaps = fs.readdirSync(BACKUP_DIR).filter(f => f.endsWith('.json')).sort();
      while (allSnaps.length > 200) { fs.unlinkSync(path.join(BACKUP_DIR, allSnaps.shift())); }
      json(res, 200, { success: true, message: 'Snapshots created' });
    } catch(e) { json(res, 500, { error: e.message }); }
    return;
  }
  
  // POST /api/manual-backup
  if (method === 'POST' && pathname === '/api/manual-backup') {
    const body = await readBody(req);
    const { name, desc } = body || {};
    try {
      const manualDir = path.join(APP_DIR, 'data', 'drama-backups', 'manual');
      if (!fs.existsSync(manualDir)) fs.mkdirSync(manualDir, { recursive: true });
      const ts = new Date().toISOString().replace(/[:.]/g, '-');
      const safeName = (name || 'backup').replace(/[^\w\u4e00-\u9fa5\-]/g, '_');
      const tarName = safeName + '-' + ts + '.tar.gz';
      const tarPath = path.join(manualDir, tarName);
      const stageDir = '/tmp/drama-bak-stage-' + Date.now();
      if (!fs.existsSync(stageDir)) fs.mkdirSync(stageDir, { recursive: true });
      fs.mkdirSync(path.join(stageDir, 'projects'));
      fs.mkdirSync(path.join(stageDir, 'users'));
      fs.readdirSync(DATA_DIR).filter(f => f.endsWith('.json')).forEach(f => { try { fs.copyFileSync(path.join(DATA_DIR, f), path.join(stageDir, 'projects', f)); } catch(e) {} });
      if (fs.existsSync(USER_DIR)) { fs.readdirSync(USER_DIR).filter(f => f.endsWith('.json')).forEach(f => { try { fs.copyFileSync(path.join(USER_DIR, f), path.join(stageDir, 'users', f)); } catch(e) {} }); }
      execSync('tar czf "' + tarPath + '" -C "' + stageDir + '" .', { stdio: 'ignore', timeout: 30000 });
      fs.rmSync(stageDir, { recursive: true, force: true });
      // Save description
      if (desc) fs.writeFileSync(tarPath + '.note', desc, 'utf-8');
      json(res, 200, { success: true, name: tarName });
    } catch(e) { json(res, 500, { error: e.message }); }
    return;
  }
  
  // GET /api/manual-backups
  if (method === 'GET' && pathname === '/api/manual-backups') {
    try {
      const manualDir = path.join(APP_DIR, 'data', 'drama-backups', 'manual');
      const results = [];
      if (fs.existsSync(manualDir)) {
        fs.readdirSync(manualDir).filter(f => f.endsWith('.tar.gz')).forEach(f => {
          const stat = fs.statSync(path.join(manualDir, f));
          const notePath = path.join(manualDir, f + '.note');
          const desc = fs.existsSync(notePath) ? fs.readFileSync(notePath, 'utf-8') : '';
          results.push({ name: f, path: '/drama-backups/manual/' + f, size: stat.size, mtime: stat.mtime.toISOString(), mtimeMs: stat.mtimeMs, type: 'manual', desc });
        });
      }
      results.sort((a, b) => b.mtimeMs - a.mtimeMs);
      json(res, 200, { backups: results });
    } catch(e) { json(res, 200, { backups: [], error: e.message }); }
    return;
  }
  
  // GET /api/list-backups
  if (method === 'GET' && pathname === '/api/list-backups') {
    try {
      const backupRoot = path.join(APP_DIR, 'data', 'drama-backups');
      const results = [];
      // Manual
      const manualDir = path.join(backupRoot, 'manual');
      if (fs.existsSync(manualDir)) {
        fs.readdirSync(manualDir).filter(f => f.endsWith('.tar.gz')).forEach(f => {
          const stat = fs.statSync(path.join(manualDir, f));
          const notePath = path.join(manualDir, f + '.note');
          const desc = fs.existsSync(notePath) ? fs.readFileSync(notePath, 'utf-8') : '';
          results.push({ name: f, path: path.join(manualDir, f), size: stat.size, mtime: stat.mtime.toISOString(), mtimeMs: stat.mtimeMs, type: 'manual', desc });
        });
      }
      // Daily
      const dailyDir = path.join(backupRoot, 'daily');
      if (fs.existsSync(dailyDir)) {
        fs.readdirSync(dailyDir).filter(f => f.endsWith('.tar.gz')).forEach(f => {
          const stat = fs.statSync(path.join(dailyDir, f));
          results.push({ name: f, path: path.join(dailyDir, f), size: stat.size, mtime: stat.mtime.toISOString(), mtimeMs: stat.mtimeMs, type: 'daily' });
        });
      }
      // Snapshots
      const snapDir = path.join(backupRoot, 'snapshots');
      if (fs.existsSync(snapDir)) {
        const snaps = fs.readdirSync(snapDir).filter(f => f.endsWith('.json')).sort().reverse().slice(0, 15);
        snaps.forEach(f => {
          const stat = fs.statSync(path.join(snapDir, f));
          results.push({ name: f, path: path.join(snapDir, f), size: stat.size, mtime: stat.mtime.toISOString(), mtimeMs: stat.mtimeMs, type: 'snapshot' });
        });
      }
      results.sort((a, b) => b.mtimeMs - a.mtimeMs);
      json(res, 200, { backups: results });
    } catch(e) { json(res, 200, { backups: [], error: e.message }); }
    return;
  }
  
  // POST /api/restore-backup
  if (method === 'POST' && pathname === '/api/restore-backup') {
    const body = await readBody(req);
    const { path: backupPath } = body || {};
    if (!backupPath || !fs.existsSync(backupPath)) { json(res, 400, { error: 'File not found' }); return; }
    try {
      const stageDir = '/tmp/drama-restore-stage-' + Date.now();
      if (!fs.existsSync(stageDir)) fs.mkdirSync(stageDir, { recursive: true });
      execSync('tar xzf "' + backupPath + '" -C "' + stageDir + '"', { stdio: 'pipe', timeout: 30000 });
      // Handle both structured (projects/ subdir) and flat (legacy) formats
      const projDir = path.join(stageDir, 'projects');
      if (fs.existsSync(projDir)) {
        fs.readdirSync(projDir).filter(f => f.endsWith('.json')).forEach(f => {
          const src = path.join(projDir, f);
          const dst = path.join(DATA_DIR, f);
          // Backup before overwrite
          if (fs.existsSync(dst)) {
            const bakTs = new Date().toISOString().replace(/[:.]/g, '-');
            fs.copyFileSync(dst, dst + '.pre-restore.' + bakTs);
          }
          fs.copyFileSync(src, dst);
        });
      } else {
        // Flat format
        fs.readdirSync(stageDir).filter(f => f.endsWith('.json') && !f.includes('users')).forEach(f => {
          const src = path.join(stageDir, f);
          const dst = path.join(DATA_DIR, f);
          if (fs.existsSync(dst)) { const bakTs = new Date().toISOString().replace(/[:.]/g, '-'); fs.copyFileSync(dst, dst + '.pre-restore.' + bakTs); }
          fs.copyFileSync(src, dst);
        });
      }
      // Restore users if present
      const userDir = path.join(stageDir, 'users');
      if (fs.existsSync(userDir)) {
        fs.readdirSync(userDir).filter(f => f.endsWith('.json')).forEach(f => {
          fs.copyFileSync(path.join(userDir, f), path.join(USER_DIR, f));
        });
      }
      const flatUsers = path.join(stageDir, 'users.json');
      if (fs.existsSync(flatUsers)) fs.copyFileSync(flatUsers, USER_FILE);
      fs.rmSync(stageDir, { recursive: true, force: true });
      json(res, 200, { success: true, message: 'Restore completed' });
    } catch(e) { json(res, 500, { error: e.message }); }
    return;
  }
  
  // POST /api/run-backup
  if (method === 'POST' && pathname === '/api/run-backup') {
    try {
      const dailyDir = path.join(APP_DIR, 'data', 'drama-backups', 'daily');
      if (!fs.existsSync(dailyDir)) fs.mkdirSync(dailyDir, { recursive: true });
      const ts = new Date().toISOString().replace(/[:.]/g, '-');
      const body = await readBody(req);
      const backupName = (body && body.name) ? String(body.name).replace(/[^\w\u4e00-\u9fa5\-]/g, '_') : 'backup';
      const tarName = backupName + '-' + ts + '.tar.gz';
      const tarPath = path.join(dailyDir, tarName);
      const stageDir = '/tmp/drama-bak-stage-' + Date.now();
      if (!fs.existsSync(stageDir)) fs.mkdirSync(stageDir, { recursive: true });
      fs.mkdirSync(path.join(stageDir, 'projects'));
      fs.mkdirSync(path.join(stageDir, 'users'));
      fs.readdirSync(DATA_DIR).filter(f => f.endsWith('.json')).forEach(f => {
        try { fs.copyFileSync(path.join(DATA_DIR, f), path.join(stageDir, 'projects', f)); } catch(e) {}
      });
      if (fs.existsSync(USER_DIR)) {
        fs.readdirSync(USER_DIR).filter(f => f.endsWith('.json')).forEach(f => {
          try { fs.copyFileSync(path.join(USER_DIR, f), path.join(stageDir, 'users', f)); } catch(e) {}
        });
      }
      execSync('tar czf "' + tarPath + '" -C "' + stageDir + '" .', { stdio: 'ignore', timeout: 30000 });
      fs.rmSync(stageDir, { recursive: true, force: true });
      json(res, 200, { success: true, name: tarName });
    } catch(e) { json(res, 500, { error: e.message }); }
    return;
  }
  
  // POST /api/download-backup
  if (method === 'POST' && pathname === '/api/download-backup') {
    const body = await readBody(req);
    const filePath = (body || {}).path;
    if (!filePath || !fs.existsSync(filePath)) { json(res, 404, { error: 'File not found' }); return; }
    const stat = fs.statSync(filePath);
    const fname = path.basename(filePath);
    res.writeHead(200, { 'Content-Type': 'application/gzip', 'Content-Disposition': 'attachment; filename="' + fname + '"', 'Content-Length': stat.size });
    fs.createReadStream(filePath).pipe(res);
    return;
  }
  
  // ====== Template routes ======
  const TEMPLATES_DIR = path.join(APP_DIR, 'data', 'drama-templates');
  if (!fs.existsSync(TEMPLATES_DIR)) fs.mkdirSync(TEMPLATES_DIR, { recursive: true });
  const OVERRIDES_FILE = path.join(TEMPLATES_DIR, 'overrides.json');
  
  // GET /api/template/overrides
  if (method === 'GET' && pathname === '/api/template/overrides') {
    let overrides = {};
    if (fs.existsSync(OVERRIDES_FILE)) {
      try { overrides = JSON.parse(fs.readFileSync(OVERRIDES_FILE, 'utf-8')); } catch(e) {}
    }
    json(res, 200, { overrides });
    return;
  }
  
  // POST /api/template/save
  if (method === 'POST' && pathname === '/api/template/save') {
    const body = await readBody(req);
    let overrides = {};
    if (fs.existsSync(OVERRIDES_FILE)) {
      try { overrides = JSON.parse(fs.readFileSync(OVERRIDES_FILE, 'utf-8')); } catch(e) {}
    }
    overrides[body.key] = { title: body.title, desc: body.desc, template: body.template };
    fs.writeFileSync(OVERRIDES_FILE, JSON.stringify(overrides, null, 2), 'utf-8');
    json(res, 200, { success: true });
    return;
  }
  
  // DELETE /api/template/reset
  if (method === 'DELETE' && pathname === '/api/template/reset') {
    const body = await readBody(req);
    let overrides = {};
    if (fs.existsSync(OVERRIDES_FILE)) {
      try { overrides = JSON.parse(fs.readFileSync(OVERRIDES_FILE, 'utf-8')); } catch(e) {}
    }
    delete overrides[body.key];
    fs.writeFileSync(OVERRIDES_FILE, JSON.stringify(overrides, null, 2), 'utf-8');
    json(res, 200, { success: true });
    return;
  }
  
  // 404
  json(res, 404, { error: 'Not found' });
});

const PORT = 3002;
server.listen(PORT, () => {
  console.log(`🎬 桌面版漫剧服务运行中 http://localhost:${PORT}`);
  console.log(`📁 数据目录: ${DATA_DIR}`);
});
