import { BrowserWindow, ipcMain, app, shell } from "electron";
import type { UpdateInfo as ElectronUpdateInfo } from "electron-updater";
import { autoUpdater } from "electron-updater";
import fs from "fs";
import path from "path";
import https from "https";
import http from "http";
import { createUpgradeDataSnapshot } from "./services/upgrade-backup";
import {
  compareVersions,
  isPrereleaseVersion,
} from "../utils/version";

// Simplified update info type (for IPC transmission)
// 简化的更新信息类型（用于 IPC 传输）
interface SimpleUpdateInfo {
  version: string;
  releaseNotes?: string;
  releaseDate?: string;
}

interface ProgressInfo {
  percent: number;
  bytesPerSecond: number;
  total: number;
  transferred: number;
}

export type MacInstallSource = "direct" | "homebrew" | "unknown";

type UpdateChannel = "stable" | "preview";

interface UpdateRequestOptions {
  useMirror?: boolean;
  channel?: UpdateChannel;
}

const OFFICIAL_REPO = {
  provider: "github" as const,
  owner: "legeling",
  repo: "PromptHub",
  releaseType: "release" as const,
};

function normalizeUpdateOptions(
  input?: boolean | UpdateRequestOptions,
): Required<UpdateRequestOptions> {
  if (typeof input === "boolean") {
    return { useMirror: input, channel: "stable" };
  }
  return {
    useMirror: Boolean(input?.useMirror),
    channel: input?.channel === "preview" ? "preview" : "stable",
  };
}

function getFeedSuffix(channel: UpdateChannel, releaseTag?: string): string {
  if (channel === "preview" && releaseTag) {
    return `download/${releaseTag}`;
  }
  return "latest/download";
}

function getMirrorSources(channel: UpdateChannel, releaseTag?: string): string[] {
  const suffix = getFeedSuffix(channel, releaseTag);
  return [
    `https://ghfast.top/https://github.com/legeling/PromptHub/releases/${suffix}`,
    `https://gh-proxy.com/https://github.com/legeling/PromptHub/releases/${suffix}`,
    `https://hub.gitmirror.com/https://github.com/legeling/PromptHub/releases/${suffix}`,
    `https://cors.isteed.cc/github.com/legeling/PromptHub/releases/${suffix}`,
  ];
}

function getOfficialFeedUrl(channel: UpdateChannel, releaseTag?: string): string {
  return `https://github.com/${OFFICIAL_REPO.owner}/${OFFICIAL_REPO.repo}/releases/${getFeedSuffix(channel, releaseTag)}`;
}

function getOfficialFeedConfig(channel: UpdateChannel, releaseTag?: string) {
  if (channel === "preview") {
    return {
      provider: "generic" as const,
      channel: getGenericChannelName(),
      url: getOfficialFeedUrl(channel, releaseTag),
    };
  }
  return OFFICIAL_REPO;
}

function applyMirrorDownloadSettings(useMirror: boolean) {
  const updater = autoUpdater as unknown as {
    useMultipleRangeRequest?: boolean;
  };
  updater.useMultipleRangeRequest = !useMirror;
}


interface FeedContext {
  channel: UpdateChannel;
  releaseTag?: string;
}

let lastFeedContext: FeedContext = { channel: "stable" };

async function fetchLatestPreviewReleaseTag(): Promise<string | null> {
  return await new Promise<string | null>((resolve, reject) => {
    const request = https.get(
      {
        hostname: "api.github.com",
        path: `/repos/${OFFICIAL_REPO.owner}/${OFFICIAL_REPO.repo}/releases?per_page=20`,
        headers: {
          Accept: "application/vnd.github+json",
          "User-Agent": "PromptHub-Updater",
        },
      },
      (response) => {
        let body = "";
        response.setEncoding("utf8");

        response.on("data", (chunk: string) => {
          body += chunk;
        });

        response.on("end", () => {
          if (response.statusCode !== 200) {
            reject(
              new Error(
                `Preview release lookup failed with HTTP ${response.statusCode || 0}`,
              ),
            );
            return;
          }

          try {
            const releases = JSON.parse(body) as Array<{
              draft?: boolean;
              prerelease?: boolean;
              tag_name?: string;
            }>;
            const latestPreview = releases.find(
              (release) =>
                release.prerelease === true &&
                release.draft !== true &&
                typeof release.tag_name === "string" &&
                release.tag_name.length > 0,
            );
            resolve(latestPreview?.tag_name || null);
          } catch (error) {
            reject(error instanceof Error ? error : new Error(String(error)));
          }
        });
      },
    );

    request.on("error", (error) => reject(error));
    request.setTimeout(15000, () => {
      request.destroy(new Error("Preview release lookup timed out"));
    });
  });
}

async function resolveFeedContext(channel: UpdateChannel): Promise<FeedContext> {
  if (channel !== "preview") {
    return { channel };
  }

  // Preview channel is intentionally prerelease-only. Stable releases are not
  // fallback candidates; users return to stable updates by switching channels.
  const releaseTag = await fetchLatestPreviewReleaseTag();
  if (!releaseTag) {
    throw new Error(
      "Update check failed: No published prerelease preview release is currently available.",
    );
  }

  return { channel, releaseTag };
}

function applyUpdaterPreferences(channel: UpdateChannel): void {
  autoUpdater.allowPrerelease = channel === "preview";
  autoUpdater.allowDowngrade = false;
}

function isRemoteVersionNewer(remoteVersion: string, currentVersion: string): boolean {
  return compareVersions(remoteVersion, currentVersion) > 0;
}

function getGenericChannelName(): string | undefined {
  if (process.platform === "win32" && process.arch === "arm64") {
    return "latest-arm64";
  }

  return undefined;
}

function filterDowngradeStatus(info: ElectronUpdateInfo): boolean {
  const currentVersion = app.getVersion();
  const isNewer = isRemoteVersionNewer(info.version, currentVersion);

  if (!isNewer) {
    console.info(
      `[Updater] Ignoring downgrade/non-upgrade candidate ${info.version} for current ${currentVersion}`,
    );
  }

  return isNewer;
}

function applyFeedContext(useMirror: boolean, context: FeedContext, mirrorUrl?: string): void {
  if (useMirror) {
    autoUpdater.setFeedURL({
      provider: "generic",
      channel: getGenericChannelName(),
      url: mirrorUrl || getMirrorSources(context.channel, context.releaseTag)[0],
    });
    return;
  }

  autoUpdater.setFeedURL(
    getOfficialFeedConfig(context.channel, context.releaseTag),
  );
}

function toCheckResult(result: unknown): {
  success: boolean;
  result?: unknown;
  updateAvailable?: boolean;
} {
  const isUpdateAvailable = Boolean(
    result &&
      typeof result === "object" &&
      "isUpdateAvailable" in result &&
      (result as { isUpdateAvailable?: boolean }).isUpdateAvailable,
  );

  return {
    success: true,
    result,
    updateAvailable: isUpdateAvailable,
  };
}

export { compareVersions };

// Read changelog for specified version range from CHANGELOG.md
// 从 CHANGELOG.md 读取指定版本区间的更新日志
export function getChangelogForVersionRange(
  newVersion: string,
  currentVersion: string,
): string {
  try {
    const isDev = process.env.NODE_ENV === "development" || !app.isPackaged;
    let changelogPath: string;

    if (isDev) {
      changelogPath = path.join(__dirname, "../../../../CHANGELOG.md");
    } else {
      // Check if resourcesPath exists (may be undefined in test environment)
      // 检查 resourcesPath 是否存在（在测试环境中可能为 undefined）
      if (!process.resourcesPath) {
        return "";
      }
      // After packaging, CHANGELOG.md is in resources directory
      // 打包后，CHANGELOG.md 在 resources 目录
      changelogPath = path.join(process.resourcesPath, "CHANGELOG.md");
      // If not exists, try app.asar.unpacked
      // 如果不存在，尝试 app.asar.unpacked
      if (!fs.existsSync(changelogPath)) {
        changelogPath = path.join(
          process.resourcesPath,
          "app.asar.unpacked",
          "CHANGELOG.md",
        );
      }
      // Still not exists, try app directory
      // 还不存在，尝试 app 目录
      if (!fs.existsSync(changelogPath)) {
        changelogPath = path.join(app.getAppPath(), "CHANGELOG.md");
      }
    }

    if (!fs.existsSync(changelogPath)) {
      console.warn("[Updater] CHANGELOG.md not found at:", changelogPath);
      console.warn("[Updater] isDev:", isDev);
      console.warn("[Updater] __dirname:", __dirname);
      console.warn("[Updater] resourcesPath:", process.resourcesPath);
      console.warn("[Updater] appPath:", app.getAppPath());
      return "";
    }

    console.log("[Updater] Reading CHANGELOG from:", changelogPath);

    const content = fs.readFileSync(changelogPath, "utf-8");

    // Parse CHANGELOG, extract all updates within version range
    // Format: ## [0.2.9] - 2025-12-18
    // 解析 CHANGELOG，提取版本区间内的所有更新
    // 格式: ## [0.2.9] - 2025-12-18
    const versionRegex = /^## \[(\d+\.\d+\.\d+(?:-[a-zA-Z0-9.]+)?)\]/gm;
    const versions: { version: string; startIndex: number }[] = [];

    let match;
    while ((match = versionRegex.exec(content)) !== null) {
      versions.push({
        version: match[1],
        startIndex: match.index,
      });
    }

    // Find versions to include (greater than currentVersion and less than or equal to newVersion)
    // 找到需要包含的版本（大于 currentVersion 且小于等于 newVersion）
    const relevantSections: string[] = [];

    for (let i = 0; i < versions.length; i++) {
      const ver = versions[i].version;
      // Version is in (currentVersion, newVersion] range
      // 版本在 (currentVersion, newVersion] 区间内
      if (
        compareVersions(ver, currentVersion) > 0 &&
        compareVersions(ver, newVersion) <= 0
      ) {
        const startIndex = versions[i].startIndex;
        const endIndex = versions[i + 1]?.startIndex || content.length;
        let section = content.slice(startIndex, endIndex).trim();

        // Remove separator lines
        // 移除分隔线
        section = section.replace(/^---\s*$/gm, "").trim();

        relevantSections.push(section);
      }
    }

    if (relevantSections.length === 0) {
      return "";
    }

    return relevantSections.join("\n\n---\n\n");
  } catch (error) {
    console.error("Failed to read CHANGELOG.md:", error);
    return "";
  }
}

// Convert from electron-updater's UpdateInfo to simplified format
// 从 electron-updater 的 UpdateInfo 转换为简化格式
function toSimpleInfo(info: ElectronUpdateInfo): SimpleUpdateInfo {
  const currentVersion = app.getVersion();

  // Prefer reading version range changelog from CHANGELOG.md
  // 优先从 CHANGELOG.md 读取版本区间的更新日志
  let releaseNotes = getChangelogForVersionRange(info.version, currentVersion);

  // If CHANGELOG has no content, fallback to GitHub Release notes
  // 如果 CHANGELOG 没有内容，回退到 GitHub Release 的说明
  if (!releaseNotes) {
    let githubNotes = "";
    if (typeof info.releaseNotes === "string") {
      githubNotes = info.releaseNotes;
    } else if (Array.isArray(info.releaseNotes)) {
      githubNotes = info.releaseNotes
        .map((n) => (n.note ? n.note : ""))
        .filter(Boolean)
        .join("\n\n");
    }

    // Check if GitHub notes is the full CHANGELOG (contains multiple version headers)
    // 检查 GitHub notes 是否是完整的 CHANGELOG（包含多个版本标题）
    const versionHeaders = githubNotes.match(/^## \[\d+\.\d+\.\d+/gm) || [];

    if (versionHeaders.length > 3) {
      // Likely full CHANGELOG, try to extract version range
      // 可能是完整的 CHANGELOG，尝试提取版本区间
      console.log(
        "[Updater] GitHub notes appears to be full CHANGELOG, extracting version range...",
      );
      releaseNotes = extractVersionRange(
        githubNotes,
        info.version,
        currentVersion,
      );
    } else {
      releaseNotes = githubNotes;
    }
  }

  return {
    version: info.version,
    releaseNotes,
    releaseDate: info.releaseDate,
  };
}

// Extract version range from CHANGELOG content (for fallback)
// 从 CHANGELOG 内容中提取版本区间（用于 fallback）
function extractVersionRange(
  content: string,
  newVersion: string,
  currentVersion: string,
): string {
  const versionRegex = /^## \[(\d+\.\d+\.\d+(?:-[a-zA-Z0-9.]+)?)\]/gm;
  const versions: { version: string; startIndex: number }[] = [];

  let match;
  while ((match = versionRegex.exec(content)) !== null) {
    versions.push({
      version: match[1],
      startIndex: match.index,
    });
  }

  const relevantSections: string[] = [];

  for (let i = 0; i < versions.length; i++) {
    const ver = versions[i].version;
    if (
      compareVersions(ver, currentVersion) > 0 &&
      compareVersions(ver, newVersion) <= 0
    ) {
      const startIndex = versions[i].startIndex;
      const endIndex = versions[i + 1]?.startIndex || content.length;
      let section = content.slice(startIndex, endIndex).trim();
      section = section.replace(/^---\s*$/gm, "").trim();
      relevantSections.push(section);
    }
  }

  if (relevantSections.length === 0) {
    // If no relevant sections, just return the first version's content
    // 如果没有相关版本，返回第一个版本的内容
    if (versions.length > 0) {
      const startIndex = versions[0].startIndex;
      const endIndex = versions[1]?.startIndex || content.length;
      return content
        .slice(startIndex, endIndex)
        .trim()
        .replace(/^---\s*$/gm, "")
        .trim();
    }
    return content;
  }

  return relevantSections.join("\n\n---\n\n");
}

let mainWindow: BrowserWindow | null = null;
let lastPercent = 0; // Track last progress to prevent regression
// 跟踪上次进度，防止进度回退

const isMac = process.platform === "darwin";

function normalizeRealPath(inputPath: string): string {
  try {
    return fs.realpathSync(inputPath);
  } catch {
    return inputPath;
  }
}

export function detectMacInstallSource(executablePath: string = process.execPath): MacInstallSource {
  if (!isMac) {
    return "unknown";
  }

  const resolvedPath = normalizeRealPath(executablePath);
  const normalizedPath = resolvedPath.replace(/\\/g, "/");

  if (
    normalizedPath.includes("/Caskroom/") ||
    normalizedPath.startsWith("/opt/homebrew/Caskroom/") ||
    normalizedPath.startsWith("/usr/local/Caskroom/")
  ) {
    return "homebrew";
  }

  return "direct";
}

function getMacInstallSource(): MacInstallSource {
  return detectMacInstallSource(process.execPath);
}

function getHomebrewUpgradeMessage(): string {
  return (
    "This PromptHub build appears to be installed via Homebrew. " +
    "Please upgrade it with 'brew upgrade --cask prompthub' instead of using the in-app DMG updater."
  );
}

// macOS: track last detected update info for DMG download
// macOS: 记录最近一次检测到的更新信息，用于 DMG 下载
let lastUpdateInfo: ElectronUpdateInfo | null = null;
// macOS: path to the downloaded DMG file
// macOS: 已下载的 DMG 文件路径
let macDownloadedDmgPath: string | null = null;

export interface UpdateStatus {
  status:
    | "checking"
    | "available"
    | "not-available"
    | "downloading"
    | "downloaded"
    | "error";
  info?: SimpleUpdateInfo;
  progress?: ProgressInfo;
  error?: string;
}

export function initUpdater(win: BrowserWindow) {
  mainWindow = win;

  // Disable auto download, let user choose
  // 禁用自动下载，让用户选择
  autoUpdater.autoDownload = false;
  // Disable auto-install on quit across all platforms.
  //
  // Historical context: v0.5.2 enabled this on Windows (`!isMac`), but when
  // combined with the auto-recovery code path in renderer (which called
  // `app.relaunch()+quit()` after copying a recovered database), a pending
  // electron-updater install was triggered on every quit. That install
  // silently re-applied the same NSIS package — and because the package
  // itself could lead the app back into an empty-database state, the loop
  // repeated indefinitely.
  //
  // Requiring an explicit user click to install is worth the minor UX cost.
  // See AGENTS.md §12 and the v0.5.3 regression analysis.
  autoUpdater.autoInstallOnAppQuit = false;

  applyUpdaterPreferences(isPrereleaseVersion(app.getVersion()) ? "preview" : "stable");
  console.log(
    `[Updater] Platform: ${process.platform}, Arch: ${process.arch}, currentVersion: ${app.getVersion()}`,
  );

  // Update check error
  // 检查更新出错
  autoUpdater.on("error", (error) => {
    console.error("Update error:", error);
    let message = (error && (error as Error).message) || String(error);
    // Handle 404 error for missing yml files
    // 处理找不到 yml 文件的 404 错误
    if (message.includes("404") || message.includes("Cannot find")) {
      message =
        "Update check failed: Cannot find update manifest file in the latest release.\n" +
        "更新检查失败：无法在最新版本中找到更新配置文件。\n" +
        "请前往 GitHub Releases 页面手动下载安装。";
    } else if (message.includes("ZIP file not provided")) {
      message =
        "Auto update requires ZIP installer, but current Release does not have corresponding ZIP file. Please go to GitHub Releases page to download manually, or wait for next version to fix auto update.";
      // 自动更新需要 ZIP 安装包，但当前版本的 Release 中没有对应的 ZIP 文件。请前往 GitHub Releases 页面手动下载安装，或等待下一个版本修复自动更新。
    }
    if (
      message.toLowerCase().includes("sha512") &&
      message.toLowerCase().includes("mismatch")
    ) {
      console.log(
        "[Updater] SHA512 mismatch, temp directory:",
        app.getPath("temp"),
      );
      message =
        "SHA512 校验失败\n\n" +
        "这通常是由于 CDN 缓存不一致或网络问题导致的。\n" +
        "文件可能已下载完成，您可以点击下方按钮打开文件夹手动尝试安装。";
    }
    sendStatusToWindow({
      status: "error",
      error: message,
    });
  });

  // Checking for update
  // 检查更新中
  autoUpdater.on("checking-for-update", () => {
    console.info("Checking for update...");
    sendStatusToWindow({ status: "checking" });
  });

  // Update available
  // 有可用更新
  autoUpdater.on("update-available", (info) => {
    if (!filterDowngradeStatus(info)) {
      sendStatusToWindow({
        status: "not-available",
        info: toSimpleInfo(info),
      });
      return;
    }

    console.info("Update available:", info.version);
    // macOS: store update info for later DMG download
    // macOS: 保存更新信息，供后续 DMG 下载使用
    if (isMac) {
      lastUpdateInfo = info;
      console.log(
        `[Updater/macDMG] Stored update info: v${info.version}, files:`,
        info.files?.map((f) => f.url),
      );
    }
    sendStatusToWindow({
      status: "available",
      info: toSimpleInfo(info),
    });
  });

  // No update available
  // 没有可用更新
  autoUpdater.on("update-not-available", (info) => {
    console.info("Update not available, current version is latest");
    sendStatusToWindow({
      status: "not-available",
      info: toSimpleInfo(info),
    });
  });

  // Download progress
  // 下载进度
  autoUpdater.on("download-progress", (progress: ProgressInfo) => {
    // Prevent progress regression (electron-updater resets progress when downloading multiple files)
    // 防止进度回退（electron-updater 下载多个文件时会重置进度）
    if (progress.percent < lastPercent && lastPercent < 99) {
      // Keep last progress when regression occurs
      // 进度回退时，保持上次进度
      console.info(
        `Download progress (ignored regression): ${progress.percent.toFixed(2)}% -> keeping ${lastPercent.toFixed(2)}%`,
      );
      return;
    }
    lastPercent = progress.percent;
    console.info(`Download progress: ${progress.percent.toFixed(2)}%`);
    sendStatusToWindow({
      status: "downloading",
      progress,
    });
  });

  // Download completed
  // 下载完成
  autoUpdater.on("update-downloaded", (info) => {
    console.info("Update downloaded:", info.version);
    sendStatusToWindow({
      status: "downloaded",
      info: toSimpleInfo(info),
    });
  });
}

function sendStatusToWindow(status: UpdateStatus) {
  if (mainWindow && !mainWindow.isDestroyed()) {
    mainWindow.webContents.send("updater:status", status);
  }
}

// --- macOS DMG direct download (bypass Squirrel) ---
// --- macOS DMG 直接下载（绕过 Squirrel） ---

/**
 * Find the DMG download URL from update info for current architecture.
 * electron-updater's UpdateInfo.files contains all files listed in latest-mac.yml.
 * 从更新信息中找到当前架构对应的 DMG 下载链接。
 */
function findMacDmgUrl(
  info: ElectronUpdateInfo,
  feedUrl: string,
): string | null {
  const arch = process.arch; // 'x64' or 'arm64'
  const files = info.files || [];

  // Find DMG file matching current architecture
  // 找到匹配当前架构的 DMG 文件
  const dmgFile =
    files.find((f) => f.url.endsWith(".dmg") && f.url.includes(`-${arch}.`)) ||
    files.find((f) => f.url.endsWith(".dmg"));

  if (!dmgFile) {
    console.error("[Updater/macDMG] No DMG file found in update info");
    return null;
  }

  // Build full URL: feedUrl base + filename
  // 构建完整 URL：feedUrl 基础路径 + 文件名
  const baseUrl = feedUrl.replace(/\/$/, "");
  return `${baseUrl}/${dmgFile.url}`;
}

/**
 * Download a file via HTTP(S) with redirect following and progress reporting.
 * 通过 HTTP(S) 下载文件，支持重定向跟踪和进度上报。
 */
function downloadFile(
  url: string,
  destPath: string,
  onProgress: (progress: ProgressInfo) => void,
): Promise<void> {
  return new Promise((resolve, reject) => {
    const doRequest = (requestUrl: string, redirectCount: number) => {
      if (redirectCount > 5) {
        reject(new Error("Too many redirects"));
        return;
      }

      const client = requestUrl.startsWith("https") ? https : http;
      const req = client.get(requestUrl, (res) => {
        // Handle redirects (301, 302, 303, 307, 308)
        // 处理重定向
        if (
          res.statusCode &&
          res.statusCode >= 300 &&
          res.statusCode < 400 &&
          res.headers.location
        ) {
          const redirectUrl = res.headers.location.startsWith("http")
            ? res.headers.location
            : new URL(res.headers.location, requestUrl).href;
          console.log(
            `[Updater/macDMG] Redirect ${res.statusCode} -> ${redirectUrl}`,
          );
          res.resume(); // Drain response to free socket
          doRequest(redirectUrl, redirectCount + 1);
          return;
        }

        if (res.statusCode !== 200) {
          res.resume();
          reject(new Error(`HTTP ${res.statusCode} for ${requestUrl}`));
          return;
        }

        const total = parseInt(res.headers["content-length"] || "0", 10);
        let transferred = 0;
        const startTime = Date.now();

        const fileStream = fs.createWriteStream(destPath);

        res.on("data", (chunk: Buffer) => {
          transferred += chunk.length;
          const elapsed = (Date.now() - startTime) / 1000 || 0.001;
          const bytesPerSecond = Math.round(transferred / elapsed);
          const percent = total > 0 ? (transferred / total) * 100 : 0;
          onProgress({ percent, bytesPerSecond, total, transferred });
        });

        res.pipe(fileStream);

        fileStream.on("finish", () => {
          fileStream.close();
          resolve();
        });

        fileStream.on("error", (err) => {
          fs.unlink(destPath, () => {}); // Clean up partial file
          reject(err);
        });
      });

      req.on("error", (err) => {
        fs.unlink(destPath, () => {});
        reject(err);
      });

      req.setTimeout(30000, () => {
        req.destroy();
        fs.unlink(destPath, () => {});
        reject(new Error("Download request timed out"));
      });
    };

    doRequest(url, 0);
  });
}

/**
 * Build the feed URL string that was last used (for constructing DMG download URL).
 * We derive this from the autoUpdater's internal state or from stored context.
 * 构建上次使用的 feed URL（用于拼接 DMG 下载链接）。
 */
function buildFeedUrl(useMirror: boolean, context: FeedContext): string {
  if (useMirror) {
    // Return first mirror source as starting point; caller will retry all mirrors
    // 返回第一个镜像源作为起点；调用方会逐一重试
    return getMirrorSources(context.channel, context.releaseTag)[0];
  }
  return getOfficialFeedUrl(context.channel, context.releaseTag);
}

/**
 * macOS: download DMG file directly to ~/Downloads, bypassing Squirrel.
 * macOS: 直接下载 DMG 到 ~/Downloads，绕过 Squirrel 自动更新。
 */
async function macDownloadDmg(
  useMirror: boolean,
  context: FeedContext,
): Promise<{ success: boolean; error?: string }> {
  if (!lastUpdateInfo) {
    return {
      success: false,
      error: "No update info available. Please check for updates first.",
    };
  }

  const version = lastUpdateInfo.version;
  const dmgFileName = `PromptHub-${version}-${process.arch}.dmg`;
  const destPath = path.join(app.getPath("downloads"), dmgFileName);

  // If already downloaded, skip re-download
  // 如果已经下载过，跳过重新下载
  if (fs.existsSync(destPath)) {
    const stat = fs.statSync(destPath);
    if (stat.size > 1024 * 1024) {
      // > 1MB, likely valid
      console.log(
        `[Updater/macDMG] DMG already exists at: ${destPath} (${stat.size} bytes)`,
      );
      macDownloadedDmgPath = destPath;
      sendStatusToWindow({
        status: "downloaded",
        info: toSimpleInfo(lastUpdateInfo),
      });
      return { success: true };
    }
    // Too small, probably a partial download — remove and retry
    // 太小，可能是部分下载 — 删除后重试
    fs.unlinkSync(destPath);
  }

  lastPercent = 0;

  const tryDownload = async (feedUrl: string): Promise<void> => {
    const dmgUrl = findMacDmgUrl(lastUpdateInfo!, feedUrl);
    if (!dmgUrl) {
      throw new Error("Cannot determine DMG download URL from update manifest");
    }

    console.log(`[Updater/macDMG] Downloading from: ${dmgUrl}`);
    console.log(`[Updater/macDMG] Saving to: ${destPath}`);

    await downloadFile(dmgUrl, destPath, (progress) => {
      // Apply anti-regression logic
      // 应用防进度回退逻辑
      if (progress.percent < lastPercent && lastPercent < 99) {
        return;
      }
      lastPercent = progress.percent;
      sendStatusToWindow({ status: "downloading", progress });
    });
  };

  // Mirror mode: try each mirror in order
  // 镜像模式：依次尝试每个镜像源
  if (useMirror) {
    for (const mirrorUrl of getMirrorSources(context.channel, context.releaseTag)) {
      try {
        await tryDownload(mirrorUrl);
        macDownloadedDmgPath = destPath;
        sendStatusToWindow({
          status: "downloaded",
          info: toSimpleInfo(lastUpdateInfo),
        });
        return { success: true };
      } catch (mirrorError) {
        console.warn(
          `[Updater/macDMG] Mirror download failed: ${mirrorUrl}`,
          mirrorError,
        );
        lastPercent = 0;
        // Clean up partial file before next attempt
        if (fs.existsSync(destPath)) fs.unlinkSync(destPath);
      }
    }
    return {
      success: false,
      error:
        "All mirror sources failed. Please try disabling mirror acceleration.",
    };
  }

  // Official source
  // 官方源
  try {
    const feedUrl = buildFeedUrl(false, context);
    await tryDownload(feedUrl);
    macDownloadedDmgPath = destPath;
    sendStatusToWindow({
      status: "downloaded",
      info: toSimpleInfo(lastUpdateInfo),
    });
    return { success: true };
  } catch (err) {
    const errMsg = (err as Error).message || String(err);
    return { success: false, error: `Download DMG failed: ${errMsg}` };
  }
}

// Register IPC handlers
// 注册 IPC 处理程序
const UPDATER_IPC_CHANNELS = [
  "updater:version",
  "updater:installSource",
  "updater:check",
  "updater:download",
  "updater:install",
  "updater:platform",
  "updater:openReleases",
  "updater:openDownloadedUpdate",
] as const;

export function registerUpdaterIPC() {
  const isDev = process.env.NODE_ENV === "development" || !app.isPackaged;

  if (typeof ipcMain.removeHandler === "function") {
    for (const channel of UPDATER_IPC_CHANNELS) {
      ipcMain.removeHandler(channel);
    }
  }

  // Get current version - always available
  // 获取当前版本 - 总是可用
  ipcMain.handle("updater:version", () => {
    return app.getVersion();
  });

  ipcMain.handle("updater:installSource", () => {
    return isMac ? getMacInstallSource() : "unknown";
  });

  // 检查更新
  // Check for updates - respect user's mirror preference
  ipcMain.handle("updater:check", async (_event, request?: boolean | UpdateRequestOptions) => {
    if (isDev) {
      return {
        success: false,
        error: "Update check disabled in development mode",
      };
    }

    const { useMirror, channel } = normalizeUpdateOptions(request);
    applyUpdaterPreferences(channel);
    applyMirrorDownloadSettings(useMirror);
    let context: FeedContext;

    try {
      context = await resolveFeedContext(channel);
      lastFeedContext = context;
    } catch (error) {
      return {
        success: false,
        error: error instanceof Error ? error.message : String(error),
      };
    }

    // If mirror is enabled, use mirror sources directly
    // 如果启用了镜像，直接使用镜像源（不先尝试官方）
    if (useMirror) {
      for (const mirrorUrl of getMirrorSources(context.channel, context.releaseTag)) {
        try {
          console.log(
            `[Updater] Using ${context.channel} mirror for check: ${mirrorUrl}`,
          );
          applyFeedContext(true, context, mirrorUrl);
          const result = await autoUpdater.checkForUpdates();
          console.log(`[Updater] Mirror check succeeded: ${mirrorUrl}`);
          return toCheckResult(result);
        } catch (mirrorError) {
          console.warn(`[Updater] Mirror check failed: ${mirrorUrl}`);
        }
      }
      // All mirrors failed
      return {
        success: false,
        error:
          "All mirror sources failed. Please try disabling mirror acceleration.",
      };
    }

    // Mirror disabled, use official source
    // 未启用镜像，使用官方源
    try {
      console.log(`[Updater] Using official ${context.channel} source for check`);
      applyFeedContext(false, context);
      const result = await autoUpdater.checkForUpdates();
      return toCheckResult(result);
    } catch (officialError) {
      const errMsg = (officialError as Error).message || String(officialError);
      return { success: false, error: `Update check failed: ${errMsg}` };
    }
  });

  // Start downloading update
  // 开始下载更新 - respect user's mirror preference
  ipcMain.handle("updater:download", async (_event, request?: boolean | UpdateRequestOptions) => {
    if (isDev) {
      return { success: false, error: "Download disabled in development mode" };
    }

    const { useMirror, channel } = normalizeUpdateOptions(request);
    applyUpdaterPreferences(channel);
    lastPercent = 0;
    let context: FeedContext;

    try {
      context =
        lastFeedContext.channel === channel
          ? lastFeedContext
          : await resolveFeedContext(channel);
    } catch (error) {
      return {
        success: false,
        error: error instanceof Error ? error.message : String(error),
      };
    }

    lastFeedContext = context;

    // macOS: bypass Squirrel, download DMG directly to ~/Downloads
    // macOS: 绕过 Squirrel，直接下载 DMG 到 ~/Downloads
    if (isMac) {
      if (getMacInstallSource() === "homebrew") {
        return {
          success: false,
          error: getHomebrewUpgradeMessage(),
          installSource: "homebrew",
        };
      }
      console.log("[Updater/macDMG] Using direct DMG download for macOS");
      return await macDownloadDmg(useMirror, context);
    }

    // Windows/Linux: use electron-updater's built-in download (Squirrel/NSIS)
    // Windows/Linux: 使用 electron-updater 内置下载（Squirrel/NSIS）
    applyMirrorDownloadSettings(useMirror);

    // If mirror is enabled, use mirror sources directly
    // 如果启用了镜像，直接使用镜像源
    if (useMirror) {
      for (const mirrorUrl of getMirrorSources(context.channel, context.releaseTag)) {
        try {
          console.log(
            `[Updater] Using ${context.channel} mirror for download: ${mirrorUrl}`,
          );
          applyFeedContext(true, context, mirrorUrl);
          await autoUpdater.downloadUpdate();
          return { success: true };
        } catch (mirrorError) {
          console.warn(`[Updater] Mirror download failed: ${mirrorUrl}`);
          lastPercent = 0; // Reset progress for next attempt
        }
      }
      // All mirrors failed
      return {
        success: false,
        error:
          "All mirror sources failed. Please try disabling mirror acceleration.",
      };
    }

    // Mirror disabled, use official source
    // 未启用镜像，使用官方源
    try {
      console.log(`[Updater] Using official ${context.channel} source for download`);
      applyFeedContext(false, context);
      await autoUpdater.downloadUpdate();
      return { success: true };
    } catch (officialError) {
      const errMsg = (officialError as Error).message || String(officialError);
      return { success: false, error: `Download update failed: ${errMsg}` };
    }
  });

  // Install update and restart
  // 安装更新并重启
  ipcMain.handle("updater:install", async () => {
    if (isDev) {
      return { success: false, error: "Install disabled in development mode" };
    }

    try {
      // At install time the new binary hasn't run yet, so we only know
      // `fromVersion` (the currently-running version being replaced).
      const backup = await createUpgradeDataSnapshot(app.getPath("userData"), {
        fromVersion: app.getVersion(),
      });

      if (isMac) {
        if (getMacInstallSource() === "homebrew") {
          return {
            success: false,
            manual: true,
            installSource: "homebrew",
            error: getHomebrewUpgradeMessage(),
            backupPath: backup.backupPath,
          };
        }
        // macOS: open the downloaded DMG for manual installation
        // macOS: 打开已下载的 DMG 文件让用户手动安装
        if (macDownloadedDmgPath && fs.existsSync(macDownloadedDmgPath)) {
          // Open (mount) the DMG file directly
          // 直接打开（挂载）DMG 文件
          shell.openPath(macDownloadedDmgPath);
        } else {
          // Fallback: open Downloads folder
          // 回退：打开下载文件夹
          shell.openPath(app.getPath("downloads"));
        }
        return {
          success: true,
          manual: true,
          backupPath: backup.backupPath,
        };
      }

      // Windows/Linux: auto install
      // Windows/Linux: 自动安装
      autoUpdater.quitAndInstall(false, true);
      return {
        success: true,
        manual: false,
        backupPath: backup.backupPath,
      };
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      console.error("[Updater] Failed to create upgrade backup:", error);
      return {
        success: false,
        error: `Automatic upgrade backup failed: ${message}`,
      };
    }
  });

  // Get platform info
  // 获取平台信息
  ipcMain.handle("updater:platform", () => {
    return process.platform;
  });

  // Open GitHub Releases page
  // 打开 GitHub Releases 页面
  ipcMain.handle("updater:openReleases", () => {
    shell.openExternal("https://github.com/legeling/PromptHub/releases");
  });

  ipcMain.handle("updater:openDownloadedUpdate", () => {
    // macOS: show the downloaded DMG in Finder
    // macOS: 在 Finder 中显示已下载的 DMG
    if (isMac && macDownloadedDmgPath && fs.existsSync(macDownloadedDmgPath)) {
      shell.showItemInFolder(macDownloadedDmgPath);
      return { success: true, path: macDownloadedDmgPath };
    }

    // Windows/Linux: show electron-updater's downloaded installer
    // Windows/Linux: 显示 electron-updater 下载的安装包
    const installerPath = (autoUpdater as unknown as { installerPath?: string })
      .installerPath;
    if (installerPath) {
      shell.showItemInFolder(installerPath);
      return { success: true, path: installerPath };
    }

    const downloadDir = app.getPath("downloads");
    shell.openPath(downloadDir);
    return { success: false, path: downloadDir };
  });
}
