/**
 * 共享的安装/下载逻辑
 * 被 install-manager.ts 和 update-center 共同使用
 */
import { spawn } from "node:child_process";
import { createWriteStream } from "node:fs";
import * as fs from "node:fs";
import * as path from "node:path";
import axios from "axios";
import pino from "pino";
import { findExecutable, SUPER_USER_COMMAND_CANDIDATES } from "./superuser";

const logger = pino({ name: "shared-installer" });

export const SHELL_CALLER_PATH = "/opt/spark-store/extras/shell-caller.sh";

export interface DownloadOptions {
  pkgname: string;
  metalinkUrl: string;
  filename: string;
  downloadDir: string;
  onLog?: (msg: string) => void;
  onProgress?: (progress: number) => void;
  onStatus?: (status: string) => void;
  signal?: AbortSignal;
}

export interface DownloadResult {
  filePath: string;
  downloadDir: string;
}

/**
 * 下载 metalink 文件并使用 aria2c 下载 deb 包
 * 与 install-manager.ts 中的下载逻辑保持一致
 */
export const downloadPackage = async ({
  metalinkUrl,
  filename,
  downloadDir,
  onLog,
  onProgress,
  onStatus,
  signal,
}: DownloadOptions): Promise<DownloadResult> => {
  // 1. 创建下载目录
  try {
    if (!fs.existsSync(downloadDir)) {
      fs.mkdirSync(downloadDir, { recursive: true });
    }
  } catch (err) {
    logger.error(`无法创建目录 ${downloadDir}: ${err}`);
    throw err;
  }

  const metalinkPath = path.join(downloadDir, `${filename}.metalink`);

  onLog?.(`正在获取 Metalink 文件: ${metalinkUrl}`);

  // 2. 下载 metalink 文件
  const response = await axios.get(metalinkUrl, {
    baseURL: "https://erotica.spark-app.store",
    responseType: "stream",
  });

  const writer = createWriteStream(metalinkPath);
  response.data.pipe(writer);

  await new Promise<void>((resolve, reject) => {
    writer.on("finish", resolve);
    writer.on("error", reject);
  });

  onLog?.("Metalink 文件下载完成");

  // 3. 清理下载目录中的旧文件（保留 .metalink 文件）
  const existingFiles = fs.readdirSync(downloadDir);
  for (const file of existingFiles) {
    if (file.endsWith(".metalink")) continue;
    const filePath = path.join(downloadDir, file);
    try {
      fs.unlinkSync(filePath);
      onLog?.(`已清理旧文件: ${file}`);
    } catch (err) {
      logger.warn(`清理文件失败 ${filePath}: ${err}`);
    }
  }

  // 4. 使用 aria2c 下载 deb 文件
  const aria2Args = [
    `--dir=${downloadDir}`,
    "--allow-overwrite=true",
    "--async-dns=false",
    "--summary-interval=1",
    "--connect-timeout=10",
    "--timeout=15",
    "--max-tries=3",
    "--retry-wait=5",
    "--max-concurrent-downloads=4",
    "--min-split-size=1M",
    "--lowest-speed-limit=1K",
    "--auto-file-renaming=false",
    "-M",
    metalinkPath,
  ];

  onStatus?.("downloading");

  // 下载重试逻辑：共10次，指数退避，首次3秒，末次1分钟
  const timeoutList = [
    3000, 4500, 6500, 9000, 13000, 18000, 26000, 36000, 50000, 60000,
  ];
  let retryCount = 0;
  let downloadSuccess = false;

  while (retryCount < timeoutList.length && !downloadSuccess) {
    const currentTimeout = timeoutList[retryCount];

    if (retryCount > 0) {
      onLog?.(`第 ${retryCount} 次重试下载...`);
      onProgress?.(0);
      // 重试前清理旧文件
      const retryFiles = fs.readdirSync(downloadDir);
      for (const file of retryFiles) {
        if (file.endsWith(".metalink")) continue;
        const filePath = path.join(downloadDir, file);
        try {
          fs.unlinkSync(filePath);
        } catch (cleanErr) {
          logger.warn(`重试清理文件失败 ${filePath}: ${cleanErr}`);
        }
      }
    }

    try {
      await new Promise<void>((resolve, reject) => {
        onLog?.(`启动下载: aria2c ${aria2Args.join(" ")}`);
        const child = spawn("aria2c", aria2Args);

        let lastProgressTime = Date.now();
        let lastProgress = 0;
        const progressCheckInterval = 1000;

        // 设置超时检测定时器
        const timeoutChecker = setInterval(() => {
          const now = Date.now();
          // 只在进度为0时检查超时
          if (lastProgress === 0 && now - lastProgressTime > currentTimeout) {
            clearInterval(timeoutChecker);
            child.kill();
            reject(new Error(`下载卡在0%超过 ${currentTimeout / 1000} 秒`));
          }
        }, progressCheckInterval);

        child.stdout.on("data", (data) => {
          const str = data.toString();
          // Match ( 12%) or (12%)
          const match = str.match(/[0-9]+(\.[0-9]+)?%/g);
          if (match) {
            const p = parseFloat(match.at(-1)) / 100;
            if (p > lastProgress) {
              lastProgress = p;
              lastProgressTime = Date.now();
            }
            onProgress?.(p);
          }
        });
        child.stderr.on("data", (d) => onLog?.(`aria2c: ${d}`));

        // 处理取消信号
        const abortHandler = () => {
          clearInterval(timeoutChecker);
          child.kill();
          reject(new Error("下载已取消"));
        };

        signal?.addEventListener("abort", abortHandler, { once: true });

        child.on("close", (code) => {
          clearInterval(timeoutChecker);
          signal?.removeEventListener("abort", abortHandler);

          if (code === 0) {
            onProgress?.(1);
            resolve();
          } else {
            reject(new Error(`Aria2c exited with code ${code}`));
          }
        });
        child.on("error", (err) => {
          clearInterval(timeoutChecker);
          signal?.removeEventListener("abort", abortHandler);
          reject(err);
        });
      });

      // 检查是否已取消
      if (signal?.aborted) {
        throw new Error("下载已取消");
      }
      downloadSuccess = true;
    } catch (err) {
      retryCount++;
      if (retryCount >= timeoutList.length) {
        throw new Error(`下载失败，已重试 ${timeoutList.length} 次: ${err}`);
      }
      onLog?.(`下载失败，准备重试 (${retryCount}/${timeoutList.length})`);
      // 等待2秒后重试
      await new Promise((r) => setTimeout(r, 2000));
    }
  }

  const filePath = path.join(downloadDir, filename);
  return { filePath, downloadDir };
};

export interface InstallOptions {
  pkgname: string;
  filePath: string;
  origin: "spark" | "apm";
  superUserCmd?: string;
  onLog?: (msg: string) => void;
  signal?: AbortSignal;
}

/**
 * 安装已下载的包
 * 与 install-manager.ts 中的安装逻辑保持一致
 */
export const installPackage = async ({
  filePath,
  origin,
  superUserCmd,
  onLog,
  signal,
}: InstallOptions): Promise<void> => {
  // 构建安装命令
  let execCommand = "";
  const execParams: string[] = [];

  if (origin === "spark") {
    execCommand = superUserCmd || SHELL_CALLER_PATH;
    if (superUserCmd) execParams.push(SHELL_CALLER_PATH);
    execParams.push(
      "ssinstall",
      filePath,
      "--delete-after-install",
      "--no-create-desktop-entry",
      "--native",
    );
  } else {
    // APM
    execCommand = superUserCmd || SHELL_CALLER_PATH;
    if (superUserCmd) {
      execParams.push(SHELL_CALLER_PATH);
    }
    execParams.push("apm", "ssinstall", filePath);
  }

  const cmdString = `${execCommand} ${execParams.join(" ")}`;
  onLog?.(`执行安装: ${cmdString}`);
  logger.info(`启动安装: ${cmdString}`);

  return new Promise<void>((resolve, reject) => {
    const child = spawn(execCommand, execParams, {
      shell: false,
      env: process.env,
    });

    let logBuffer = "";
    let logBufferTimer: NodeJS.Timeout | null = null;
    const LOG_FLUSH_MS = 100;

    const flushLogBuffer = () => {
      if (logBuffer.length > 0) {
        onLog?.(logBuffer);
        logBuffer = "";
      }
      logBufferTimer = null;
    };

    const bufferedSendLog = (message: string) => {
      logBuffer += message;
      if (!logBufferTimer) {
        logBufferTimer = setTimeout(flushLogBuffer, LOG_FLUSH_MS);
      }
    };

    // 处理取消信号
    const abortHandler = () => {
      child.kill();
      reject(new Error("安装已取消"));
    };

    signal?.addEventListener("abort", abortHandler, { once: true });

    child.stdout?.on("data", (data) => {
      bufferedSendLog(data.toString());
    });

    child.stderr?.on("data", (data) => {
      bufferedSendLog(data.toString());
    });

    child.on("error", (err) => {
      signal?.removeEventListener("abort", abortHandler);
      if (logBufferTimer) clearTimeout(logBufferTimer);
      flushLogBuffer();
      reject(err);
    });

    child.on("close", (code) => {
      signal?.removeEventListener("abort", abortHandler);
      if (logBufferTimer) clearTimeout(logBufferTimer);
      flushLogBuffer();

      if (code === 0) {
        resolve();
      } else {
        reject(new Error(`安装失败，退出码: ${code}`));
      }
    });
  });
};

/**
 * 检查是否有 apm 命令
 */
export const checkApmAvailable = async (): Promise<boolean> => {
  return new Promise((resolve) => {
    const child = spawn("which", ["apm"]);
    let stdout = "";
    child.stdout?.on("data", (data) => {
      stdout += data.toString();
    });
    child.on("close", (code) => {
      resolve(code === 0 && stdout.trim().length > 0);
    });
    child.on("error", () => {
      resolve(false);
    });
  });
};

/**
 * 检查提权命令
 */
export const checkSuperUserCommand = async (): Promise<string> => {
  if (process.getuid?.() === 0) return "";

  for (const command of SUPER_USER_COMMAND_CANDIDATES) {
    const superUserCmd = await findExecutable(command);
    if (superUserCmd.length > 0) {
      logger.info(`找到提升权限命令: ${superUserCmd}`);
      return superUserCmd;
    }
  }

  logger.error("没有找到提升权限的命令 pkexec!");
  return "";
};
