/**
 * GMP 模块卸载器
 * 工单编号: GH-GMP-004
 * 开发者: 培园A04 (5TH-LE-HK-A04)
 * 职责: 安全停止模块 · 备份数据 · 移除模块文件 · 注销注册
 *
 * 卸载流程:
 *   1. 检查模块是否存在
 *   2. 停止模块进程 (如果有)
 *   3. 备份模块数据到归档目录
 *   4. 移除模块文件
 *   5. 从installedModules注销
 *   6. 清理残留
 */

const path = require('path');
const fs = require('fs');
const { execFileSync } = require('child_process');
const { createLogger } = require('./lib/logger');
const { sanitizeModuleName, assertWithinBase } = require('./lib/path-guard');

const logger = createLogger('uninstaller');

class ModuleUninstaller {
  constructor(config, installedModules) {
    this.config = config;
    this.installedModules = installedModules;
  }

  /**
   * 卸载模块
   * @param {Object} opts
   * @param {string} opts.moduleName - 模块名称
   * @param {boolean} opts.keepBackup - 是否保留备份 (默认true)
   * @param {boolean} opts.force - 是否强制卸载 (跳过停止步骤)
   * @returns {Object} 卸载结果
   */
  async uninstall({ moduleName, keepBackup, force }) {
    // 安全防线 1: 模块名白名单 + path.basename 净化 (CodeQL 公认 sanitizer)
    const safeName = sanitizeModuleName(moduleName);

    const startTime = Date.now();
    const shouldBackup = keepBackup !== false; // 默认保留备份

    logger.info('[Uninstaller] 开始卸载模块: ' + safeName + ' backup=' + shouldBackup + ' force=' + !!force);

    // Step 1: 检查模块是否存在
    const moduleInfo = this.installedModules.get(safeName);
    if (!moduleInfo) {
      throw new Error('模块未注册: ' + safeName);
    }

    const moduleDir = moduleInfo.path;
    // 安全防线 2: resolve 后校验模块路径在 modulesDir 之内 (防路径穿越)
    assertWithinBase(this.config.modulesDir, moduleDir);

    if (!fs.existsSync(moduleDir)) {
      // 文件不存在但注册表有记录 → 清理注册
      this.installedModules.delete(safeName);
      logger.warn('[Uninstaller] 模块目录不存在, 已清理注册: ' + safeName);
      return { status: 'cleaned', module: safeName, note: '目录不存在·仅清理注册' };
    }

    try {
      // Step 2: 停止模块进程
      if (!force) {
        logger.info('[Uninstaller] Step 1: 停止模块进程...');
        await this._stopModule(safeName, moduleDir);
      }

      // Step 3: 备份
      let backupPath = null;
      if (shouldBackup) {
        logger.info('[Uninstaller] Step 2: 备份模块...');
        backupPath = this._backupModule(safeName, moduleDir);
      }

      // Step 4: 移除模块文件
      logger.info('[Uninstaller] Step 3: 移除模块文件...');
      this._removeModule(moduleDir);

      // Step 5: 注销
      this.installedModules.delete(safeName);
      logger.info('[Uninstaller] Step 4: 模块已注销');

      const duration = Date.now() - startTime;
      logger.info('[Uninstaller] 模块卸载成功: ' + safeName + ' 耗时=' + duration + 'ms');

      return {
        status: 'uninstalled',
        module: safeName,
        duration: duration,
        backupPath: backupPath,
        backupKept: shouldBackup
      };

    } catch (err) {
      const duration = Date.now() - startTime;
      logger.error('[Uninstaller] 卸载失败: ' + safeName + ' -> ' + err.message + ' 耗时=' + duration + 'ms');
      throw err;
    }
  }

  /**
   * 停止模块进程
   * 约定: 模块如果有运行中的进程, 通过pidfile或pm2管理
   */
  async _stopModule(moduleName, moduleDir) {
    // 方式1: 检查pidfile
    const pidFile = path.join(moduleDir, '.pid');
    if (fs.existsSync(pidFile)) {
      try {
        const pid = parseInt(fs.readFileSync(pidFile, 'utf-8').trim(), 10);
        if (pid && !isNaN(pid)) {
          process.kill(pid, 'SIGTERM');
          logger.info('[Uninstaller] 已发送SIGTERM到PID: ' + pid);
          // 等待进程退出
          await this._waitForExit(pid, 5000);
        }
      } catch (err) {
        if (err.code === 'ESRCH') {
          logger.info('[Uninstaller] 进程已不存在 (PID文件过期)');
        } else {
          logger.warn('[Uninstaller] 停止进程警告: ' + err.message);
        }
      }
    }

    // 方式2: 尝试pm2 stop (如果使用PM2管理)
    // moduleName 已在 uninstall() 入口经过白名单校验, 此处用 execFileSync 数组形式传参
    try {
      execFileSync('pm2', ['stop', moduleName], {
        stdio: 'pipe',
        timeout: 10000
      });
    } catch (err) {
      // PM2不可用或模块不在PM2中, 忽略
    }

    // 方式3: 检查模块自带的stop脚本
    const stopScript = path.join(moduleDir, 'stop.sh');
    if (fs.existsSync(stopScript)) {
      try {
        execFileSync('bash', ['stop.sh'], {
          cwd: moduleDir,
          stdio: 'pipe',
          timeout: 15000
        });
        logger.info('[Uninstaller] 已执行模块stop脚本');
      } catch (err) {
        logger.warn('[Uninstaller] stop脚本执行警告 (exit ' + (err.status || 'unknown') + ')');
      }
    }
  }

  /**
   * 等待进程退出
   */
  _waitForExit(pid, timeout) {
    return new Promise((resolve) => {
      const start = Date.now();
      const check = () => {
        try {
          process.kill(pid, 0); // 检查进程是否存在
          if (Date.now() - start > timeout) {
            logger.warn('[Uninstaller] 进程未在超时内退出, PID=' + pid);
            resolve();
          } else {
            setTimeout(check, 500);
          }
        } catch (err) {
          // 进程已退出
          logger.info('[Uninstaller] 进程已退出, PID=' + pid);
          resolve();
        }
      };
      check();
    });
  }

  /**
   * 备份模块到归档目录
   */
  _backupModule(moduleName, moduleDir) {
    // moduleName 已在 uninstall() 入口经过白名单校验
    const archiveDir = path.join(this.config.modulesDir, '.archive');
    if (!fs.existsSync(archiveDir)) {
      fs.mkdirSync(archiveDir, { recursive: true });
    }

    const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
    const backupDir = path.join(archiveDir, moduleName + '_' + timestamp);
    // 路径校验: 备份目录必须在 archiveDir 之内
    assertWithinBase(archiveDir, backupDir);

    this._copyDir(moduleDir, backupDir);
    logger.info('[Uninstaller] 模块已备份: ' + backupDir);
    return backupDir;
  }

  /**
   * 移除模块目录
   */
  _removeModule(moduleDir) {
    try {
      fs.rmSync(moduleDir, { recursive: true, force: true });
      logger.info('[Uninstaller] 模块目录已移除: ' + moduleDir);
    } catch (err) {
      throw new Error('移除模块目录失败: ' + err.message);
    }
  }

  /**
   * 递归复制目录 (用于备份)
   */
  _copyDir(src, dest) {
    fs.mkdirSync(dest, { recursive: true });
    const entries = fs.readdirSync(src, { withFileTypes: true });
    for (const entry of entries) {
      const srcPath = path.join(src, entry.name);
      const destPath = path.join(dest, entry.name);
      if (entry.isDirectory()) {
        if (entry.name === 'node_modules' || entry.name === '.git') continue;
        this._copyDir(srcPath, destPath);
      } else {
        fs.copyFileSync(srcPath, destPath);
      }
    }
  }
}

module.exports = { ModuleUninstaller };
