// ===================== 1. 依赖引入（最顶层，按依赖顺序） =====================
const path = require("path");
const fs = require("fs");
const http = require("http");
const https = require("https");

const { pathMap } = require("@/config/index.js");
const { getDTFSetting, getHostApi } = require("@/server/utils/store");
import api from "./request";
// ===================== 2. 全局状态与可配置参数 =====================
const taskState = {
  currentPage: 1, // 当前拉取页码，从第1页开始
  pageSize: 100, // 每页拉取100条数据，与后端约定一致
  layoutStatus: "3,5", // 部分成功，排版成功
  isDownloading: false, // 任务互斥锁：防止5分钟到点后重复执行
  timer: null, // 定时器实例，用于启停控制
  total: 0, // 数据总条数，从接口实时同步
  interval: 5 * 60 * 1000, // 定时周期：5分钟执行一次
  maxBatchConcurrency: 3, // 同时处理的最大批次数量（总并发维度1）
  maxImageConcurrency: 10 // 单个批次内同时下载的最大图片数（总并发维度2）
  // 总并发峰值 = 3 × 10 = 30，生产环境安全稳定
};

// ===================== 3. 基础工具函数 =====================

/**
 * 统一错误日志格式化输出
 * @param {string} type 错误类型
 * @param {string} batchNo 批次号
 * @param {string} url 出错的图片链接
 * @param {string} message 错误详情
 */
function logDownloadError(type, batchNo, url, message) {
  console.error(
    `[自动下载失败][${type}] 批次号: ${batchNo || "未知"} | 链接: ${url ||
      "-"} | 详情: ${message}`
  );
}

/**
 * 简易并发限制器（无第三方依赖，兼容Node 12）
 * 作用：保证同时运行的异步任务不超过指定数量，完成一个自动补上下一个
 * @param {number} concurrency 最大并发数
 * @returns {Function} 接收异步函数的包装器
 */
function pLimit(concurrency) {
  let activeCount = 0; // 当前正在执行的任务数
  const queue = []; // 等待执行的任务队列

  // 尝试启动下一个任务
  const next = () => {
    if (queue.length > 0 && activeCount < concurrency) {
      activeCount++;
      const task = queue.shift();
      task().finally(() => {
        activeCount--;
        next(); // 任务完成后自动启动下一个
      });
    }
  };

  return fn => {
    return new Promise((resolve, reject) => {
      // 加入等待队列
      queue.push(() =>
        fn()
          .then(resolve)
          .catch(reject)
      );
      next();
    });
  };
}

/**
 * 递归创建目录（异步，不阻塞主线程）
 * @param {string} dirPath 目录路径
 */
async function ensureDir(dirPath) {
  await fs.promises.mkdir(dirPath, { recursive: true });
}

/**
 * 从URL中提取原始文件名（完全沿用你指定的逻辑）
 * @param {string} url 图片链接
 * @returns {string} 原始文件名
 */
const getPngFileName = url => {
  let filePath = url.replace(/\\/g, "/"); // 统一反斜杠为正斜杠
  const originalFileName = path.basename(filePath);
  return originalFileName;
};

/**
 * 异步判断文件是否已存在
 * @param {string} filePath 文件完整路径
 * @returns {boolean} true=存在，false=不存在
 */
async function isFileExists(filePath) {
  try {
    await fs.promises.access(filePath, fs.constants.F_OK);
    return true;
  } catch (e) {
    return false;
  }
}

// ===================== 4. 核心下载能力 =====================

/**
 * 单文件流式下载（全错误捕获 + 重定向控制）
 * 流式写入：边下载边写盘，内存占用极低，不阻塞主线程
 * @param {string} url 图片下载链接
 * @param {string} savePath 本地保存完整路径
 * @param {number} redirectCount 已重定向次数（内部递归用，外部不传）
 * @returns {Promise<string>} 保存成功的文件路径
 */
function downloadFile(url, savePath, redirectCount = 0) {
  return new Promise((resolve, reject) => {
    try {
      // 根据链接协议自动选择 http/https 模块
      const request = url.startsWith("https") ? https : http;

      const req = request.get(url, { timeout: 30000 }, res => {
        // 处理重定向：最多3次，防止死循环
        if ([301, 302, 307, 308].includes(res.statusCode)) {
          if (redirectCount >= 3) {
            res.resume(); // 消耗响应体，释放连接
            reject(new Error("重定向次数超过3次，终止下载"));
            return;
          }
          // 递归执行下载，重定向次数+1
          downloadFile(res.headers.location, savePath, redirectCount + 1)
            .then(resolve)
            .catch(reject);
          return;
        }

        // HTTP状态码非200直接报错
        if (res.statusCode !== 200) {
          res.resume();
          reject(new Error(`HTTP状态码异常: ${res.statusCode}`));
          return;
        }

        // 创建写入流，通过管道将网络响应写入文件
        const writeStream = fs.createWriteStream(savePath);
        res.pipe(writeStream);

        // 写入完成
        writeStream.on("finish", () => {
          writeStream.close(() => resolve(savePath));
        });

        // 写入失败：销毁流并清理损坏文件
        writeStream.on("error", err => {
          writeStream.destroy();
          fs.promises.unlink(savePath).catch(() => {});
          reject(new Error(`文件写入失败: ${err.message}`));
        });

        // 响应流异常
        res.on("error", err => {
          writeStream.destroy();
          fs.promises.unlink(savePath).catch(() => {});
          reject(new Error(`网络响应异常: ${err.message}`));
        });
      });

      // 请求级错误（网络不通、DNS失败等）
      req.on("error", err => reject(new Error(`网络请求失败: ${err.message}`)));

      // 请求超时
      req.on("timeout", () => {
        req.destroy();
        reject(new Error("下载超时(30s)"));
      });
    } catch (syncErr) {
      // 捕获URL格式错误等同步异常
      reject(new Error(`请求初始化失败: ${syncErr.message}`));
    }
  });
}

// ===================== 6. 核心业务逻辑 =====================

/**
 * 处理单个批次的图片下载
 * 单张图片失败仅跳过当前文件，不影响同批次其他图片
 * @param {object} item 单条排版数据（含batchArrangeNum、url字段）
 */
async function processBatchItem(item) {
  const { batchArrangeNum, url: urlStr } = item;

  // ---------- 基础参数校验 ----------
  if (!batchArrangeNum) {
    logDownloadError(
      "参数无效",
      batchArrangeNum,
      "",
      "缺少批次号batchArrangeNum"
    );
    return;
  }
  if (!urlStr || typeof urlStr !== "string") {
    logDownloadError(
      "参数无效",
      batchArrangeNum,
      urlStr,
      "url字段为空或非字符串格式"
    );
    return;
  }

  // ---------- 1. 创建批次保存目录 ----------
  let saveDir;
  try {
    const downloadRoot = getDTFSetting().downloadDir;
    if (!downloadRoot) throw new Error("下载根目录downloadDir配置为空");

    // 目录结构：根目录/批次号：xxx/自动排版素材
    saveDir = path.join(
      downloadRoot,
      `批次号：${batchArrangeNum}`,
      "自动排版素材"
    );
    await ensureDir(saveDir);
  } catch (dirErr) {
    // 目录创建失败：整个批次跳过
    logDownloadError("目录创建失败", batchArrangeNum, "", dirErr.message);
    return;
  }

  // ---------- 2. 拆分图片链接 ----------
  // 支持逗号分隔的多图字符串，过滤空值与无效值
  const urlList = urlStr
    .split(",")
    .map(u => u.trim())
    .filter(u => {
      if (!u || typeof u !== "string") {
        logDownloadError("链接无效", batchArrangeNum, u, "空链接或非字符串");
        return false;
      }
      return true;
    });

  if (urlList.length === 0) {
    logDownloadError("无有效链接", batchArrangeNum, "", "拆分后无可用图片链接");
    return;
  }

  // ---------- 3. 并发下载批次内所有图片 ----------
  const imageLimit = pLimit(taskState.maxImageConcurrency);
  const fileEnv = getHostApi().fileApiUrl;
  const downloadPromises = urlList.map(url =>
    imageLimit(async () => {
      const newUrl = `${fileEnv}${url}`;

      try {
        const fileName = getPngFileName(url);
        const savePath = path.join(saveDir, fileName);

        // 文件已存在则直接跳过，不重复下载
        const exists = await isFileExists(savePath);
        if (exists) {
          console.log(
            `[跳过下载] 文件已存在 | 批次: ${batchArrangeNum} | 文件: ${fileName}`
          );
          return;
        }
        // 执行下载
        await downloadFile(newUrl, savePath);
        console.log(`[下载成功] 批次: ${batchArrangeNum} | 文件: ${fileName}`);
      } catch (downloadErr) {
        // 单张下载失败：记录日志，跳过当前文件
        logDownloadError(
          "文件下载失败",
          batchArrangeNum,
          newUrl,
          downloadErr.message
        );
      }
    })
  );

  // 等待当前批次所有图片处理完成
  await Promise.all(downloadPromises);
}

/**
 * 执行一次完整的定时任务
 * 流程：获取设置 → 判断策略 → 拉取列表 → 批量下载 → 更新页码
 */
async function runTask() {
  // 互斥锁：上一轮任务未完成则直接跳过本轮
  if (taskState.isDownloading) {
    console.log("当前有下载任务进行中，跳过本轮定时任务");
    return;
  }

  try {
    taskState.isDownloading = true;
    console.log(`=== 开始执行定时任务，当前页码：${taskState.currentPage} ===`);

    // 步骤1：调用获取设置接口
    const settingRes = await api.post(pathMap["productionConfig"]);

    const autoLayoutStrategy = settingRes.data.autoLayoutStrategy;

    // 步骤2：策略判断：不等于3才继续下载
    if (autoLayoutStrategy === 3) {
      console.log("不自动排版，跳过本次下载");
      return;
    }

    // 步骤3：调用排版数据接口，拉取当前页数据
    const layoutRes = await api.post(pathMap["productionSoftware"], {
      currentPage: taskState.currentPage,
      pageSize: taskState.pageSize,
      layoutStatus: taskState.layoutStatus
    });
    console.log(layoutRes);

    // return;
    const list = layoutRes.data.records || [];
    const total = layoutRes.data.total || 0;
    taskState.total = total;

    if (list.length === 0) {
      console.log("当前页无数据，本轮任务结束");
      return;
    }

    // 步骤4：多批次并行下载
    const batchLimit = pLimit(taskState.maxBatchConcurrency);
    const batchPromises = list.map(item =>
      batchLimit(() => processBatchItem(item))
    );
    await Promise.all(batchPromises);

    console.log(`=== 第 ${taskState.currentPage} 页全部处理完成 ===`);

    // 步骤5：更新页码，超过总数则从第1页循环
    taskState.currentPage++;
    if ((taskState.currentPage - 1) * taskState.pageSize >= taskState.total) {
      taskState.currentPage = 1;
      console.log("已遍历全部数据，下轮任务从第1页重新开始");
    }
  } catch (err) {
    // 整体任务异常（接口挂了等），记录日志
    console.error("定时任务执行异常：", err.message);
  } finally {
    // 无论成功失败，最终释放锁，避免死锁
    taskState.isDownloading = false;
    console.log("本轮任务结束，锁已释放");
  }
}

// ===================== 7. 对外暴露的启停方法 =====================

/**
 * 启动自动下载定时任务
 * 调用后立即启动定时器，5分钟后首次执行任务
 */
function startAutoDownloadTask() {
  // 防止重复启动
  if (taskState.timer) return;

  console.log("自动下载定时任务已启动，5分钟后首次执行");
  taskState.timer = setInterval(runTask, taskState.interval);
}

/**
 * 停止自动下载定时任务
 * 窗口关闭、应用退出时调用，清理定时器与状态
 */
function stopAutoDownloadTask() {
  if (taskState.timer) {
    clearInterval(taskState.timer);
    taskState.timer = null;
    taskState.isDownloading = false;
    console.log("自动下载定时任务已停止");
  }
}

// ===================== 8. 模块导出 =====================
export default {
  startAutoDownloadTask,
  stopAutoDownloadTask
};
