const fs = require("fs");
const path = require("path");
const https = require("https");

function createDownloadQueue(options = {}) {
  // 配置项
  const config = Object.assign(
    {
      concurrency: 2,
      retryTimes: 2,
      basePath: "", // 根目录，外部传入
      fileNameRule: task => `${task.batchArrangeNum}_素材.zip`, // 默认文件名规则：批次号_素材.zip
      onSuccess: () => {},
      onError: () => {},
      onIdle: () => {}
    },
    options
  );

  // 内部状态
  let pendingQueue = [];
  let runningCount = 0;
  const successSet = new Set();
  const runningSet = new Set();
  let isDestroyed = false;

  // 工具方法
  const hasFile = targetPath => fs.existsSync(targetPath);
  const cleanFile = targetPath =>
    hasFile(targetPath) && fs.unlinkSync(targetPath);

  // 流式下载核心
  const streamDownload = (url, targetPath) =>
    new Promise((resolve, reject) => {
      const ws = fs.createWriteStream(targetPath);
      const req = https.get(url, res => {
        // 处理重定向
        if ([301, 302, 307, 308].includes(res.statusCode)) {
          ws.close();
          streamDownload(res.headers.location, targetPath)
            .then(resolve)
            .catch(reject);
          return;
        }
        if (res.statusCode !== 200) {
          ws.close();
          cleanFile(targetPath);
          reject(new Error(`状态码 ${res.statusCode}`));
          return;
        }
        res.pipe(ws);
      });

      ws.on("finish", () => {
        ws.close();
        resolve();
      });
      ws.on("error", err => {
        ws.close();
        cleanFile(targetPath);
        reject(err);
      });
      req.on("error", err => {
        ws.close();
        cleanFile(targetPath);
        reject(err);
      });
      req.setTimeout(30000, () => {
        req.destroy();
        ws.close();
        cleanFile(targetPath);
        reject(new Error("下载超时"));
      });
    });

  // 队列消费
  const consume = () => {
    if (isDestroyed) return;
    if (runningCount >= config.concurrency || pendingQueue.length === 0) {
      runningCount === 0 && config.onIdle();
      return;
    }

    const task = pendingQueue.shift();
    runningCount++;
    (async () => {
      try {
        // 自动创建当前批次的目录（仅创建需要下载的批次目录，不批量预创建）
        const targetDir = path.dirname(task.targetPath);
        if (!hasFile(targetDir)) fs.mkdirSync(targetDir, { recursive: true });

        // 获取下载地址
        const url =
          typeof task.fetchUrl === "function"
            ? await task.fetchUrl(task)
            : task.downloadUrl;
        await streamDownload(url, task.targetPath);
        successSet.add(task.targetPath);
        config.onSuccess(task);
      } catch (err) {
        if (task.remainRetry > 0) {
          task.remainRetry--;
          pendingQueue.unshift(task);
        } else {
          config.onError(task, err);
        }
      } finally {
        runningCount--;
        runningSet.delete(task.targetPath);
        consume();
      }
    })();
  };

  // 对外方法
  return {
    addTasks(tasks = []) {
      if (isDestroyed) return;
      const validTasks = tasks
        .map(row => {
          // 按规则拼接完整路径：根目录 / 批次号：xxx / 自动排版素材 / 文件名
          const dir = path.join(
            config.basePath,
            `批次号：${row.batchArrangeNum}`,
            "自动排版素材"
          );
          const fileName = config.fileNameRule(row);
          const targetPath = path.join(dir, fileName);
          return { ...row, targetPath, remainRetry: config.retryTimes };
        })
        // 三重过滤：本地已存在、已下载成功、正在队列中
        .filter(
          task =>
            !hasFile(task.targetPath) &&
            !successSet.has(task.targetPath) &&
            !runningSet.has(task.targetPath)
        );

      if (validTasks.length === 0) return;
      validTasks.forEach(t => {
        runningSet.add(t.targetPath);
        pendingQueue.push(t);
      });
      consume();
    },

    get isRunning() {
      return runningCount > 0 || pendingQueue.length > 0;
    },

    destroy() {
      isDestroyed = true;
      pendingQueue = [];
      runningCount = 0;
      successSet.clear();
      runningSet.clear();
    }
  };
}

module.exports = createDownloadQueue;
