import { spawn } from "node:child_process";

import { BrowserWindow, ipcMain } from "electron";

import { SHELL_CALLER_PATH } from "../shared-installer";
import { findExecutable, SUPER_USER_COMMAND_CANDIDATES } from "../superuser";
import {
  buildInstalledSourceMap,
  mergeUpdateSources,
  parseApmUpgradableOutput,
  parseAptssUpgradableOutput,
  parsePrintUrisOutput,
} from "./query";
import { resolveUpdateItemIcons } from "./icons";
import {
  createUpdateCenterService,
  type StoreFilter,
  type UpdateCenterIgnorePayload,
  type UpdateCenterService,
  type UpdateCenterStartTask,
} from "./service";
import type { UpdateCenterItem } from "./types";

export interface UpdateCenterCommandResult {
  code: number;
  stdout: string;
  stderr: string;
}

export type UpdateCenterCommandRunner = (
  command: string,
  args: string[],
) => Promise<UpdateCenterCommandResult>;

export interface UpdateCenterLoadItemsResult {
  items: UpdateCenterItem[];
  warnings: string[];
}

interface RemoteAppMetadata {
  category: string;
  name?: string;
}

type StoreAppMetadataMap = Map<string, RemoteAppMetadata>;

interface RemoteCategoryAppEntry {
  Name?: string;
  Pkgname?: string;
}

const REMOTE_STORE_BASE_URL = "https://erotica.spark-app.store";
const categoryCache = new Map<string, Promise<StoreAppMetadataMap>>();

const APTSS_LIST_UPGRADABLE_COMMAND = {
  command: "bash",
  args: [
    "-lc",
    "env LANGUAGE=en_US /usr/bin/apt -c /opt/durapps/spark-store/bin/apt-fast-conf/aptss-apt.conf list --upgradable -o Dir::Etc::sourcelist=/opt/durapps/spark-store/bin/apt-fast-conf/sources.list.d/aptss.list -o Dir::Etc::sourceparts=/dev/null -o APT::Get::List-Cleanup=0 | awk 'NR>1'",
  ],
};

const DPKG_QUERY_INSTALLED_COMMAND = {
  command: "dpkg-query",
  args: [
    "-W",
    "-f=${Package}\t${db:Status-Want} ${db:Status-Status} ${db:Status-Eflag}\n",
  ],
};

const getApmPrintUrisCommand = (pkgname: string) => ({
  command: "bash",
  args: [
    "-lc",
    `amber-pm-debug /usr/bin/apt -c /opt/durapps/spark-store/bin/apt-fast-conf/aptss-apt.conf download ${pkgname} --print-uris`,
  ],
});

const getAptssPrintUrisCommand = (pkgname: string) => ({
  command: "bash",
  args: [
    "-lc",
    `/usr/bin/apt download ${pkgname} --print-uris -c /opt/durapps/spark-store/bin/apt-fast-conf/aptss-apt.conf -o Dir::Etc::sourcelist=/opt/durapps/spark-store/bin/apt-fast-conf/sources.list.d/aptss.list -o Dir::Etc::sourceparts=/dev/null`,
  ],
});

const runCommandCapture: UpdateCenterCommandRunner = async (
  command,
  args,
): Promise<UpdateCenterCommandResult> =>
  await new Promise((resolve) => {
    const child = spawn(command, args, {
      shell: false,
      env: process.env,
    });

    let stdout = "";
    let stderr = "";

    child.stdout?.on("data", (data) => {
      stdout += data.toString();
    });
    child.stderr?.on("data", (data) => {
      stderr += data.toString();
    });
    child.on("error", (error) => {
      resolve({ code: -1, stdout, stderr: error.message });
    });
    child.on("close", (code) => {
      resolve({ code: code ?? -1, stdout, stderr });
    });
  });

const getCommandError = (
  label: string,
  result: UpdateCenterCommandResult,
): string | null => {
  if (result.code === 0) {
    return null;
  }

  return `${label} failed: ${result.stderr || result.stdout || `exit code ${result.code}`}`;
};

const loadApmItemMetadata = async (
  item: UpdateCenterItem,
  runCommand: UpdateCenterCommandRunner,
): Promise<
  | { item: UpdateCenterItem; warning?: undefined }
  | { item: null; warning: string }
> => {
  const printUrisCommand = getApmPrintUrisCommand(item.pkgname);
  const metadataResult = await runCommand(
    printUrisCommand.command,
    printUrisCommand.args,
  );
  const commandError = getCommandError(
    `apm metadata query for ${item.pkgname}`,
    metadataResult,
  );
  if (commandError) {
    return { item: null, warning: commandError };
  }

  const metadata = parsePrintUrisOutput(metadataResult.stdout);
  if (!metadata) {
    return {
      item: null,
      warning: `apm metadata query for ${item.pkgname} returned no package metadata`,
    };
  }

  return {
    item: {
      ...item,
      ...metadata,
    },
  };
};

const loadAptssItemMetadata = async (
  item: UpdateCenterItem,
  runCommand: UpdateCenterCommandRunner,
): Promise<
  | { item: UpdateCenterItem; warning?: undefined }
  | { item: null; warning: string }
> => {
  console.log(`[DEBUG] Loading APTSS metadata for ${item.pkgname}`);
  const printUrisCommand = getAptssPrintUrisCommand(item.pkgname);
  console.log(
    `[DEBUG] APTSS command: ${printUrisCommand.command} ${printUrisCommand.args.join(" ")}`,
  );

  const metadataResult = await runCommand(
    printUrisCommand.command,
    printUrisCommand.args,
  );
  console.log(`[DEBUG] APTSS metadata result code: ${metadataResult.code}`);
  console.log(
    `[DEBUG] APTSS metadata stdout: ${metadataResult.stdout.substring(0, 500)}`,
  );
  console.log(
    `[DEBUG] APTSS metadata stderr: ${metadataResult.stderr.substring(0, 500)}`,
  );

  const commandError = getCommandError(
    `aptss metadata query for ${item.pkgname}`,
    metadataResult,
  );
  if (commandError) {
    console.log(`[DEBUG] APTSS metadata error: ${commandError}`);
    return { item: null, warning: commandError };
  }

  const metadata = parsePrintUrisOutput(metadataResult.stdout);
  if (metadata) {
    console.log(`[DEBUG] APTSS parsed metadata:`, {
      ...metadata,
      downloadUrl: `${metadata.downloadUrl}.metalink`,
    });
  } else {
    console.log(`[DEBUG] APTSS parsed metadata:`, metadata);
  }

  if (!metadata) {
    return {
      item: null,
      warning: `aptss metadata query for ${item.pkgname} returned no package metadata`,
    };
  }

  return {
    item: {
      ...item,
      ...metadata,
    },
  };
};

const enrichApmItems = async (
  items: UpdateCenterItem[],
  runCommand: UpdateCenterCommandRunner,
): Promise<UpdateCenterLoadItemsResult> => {
  const results = await Promise.all(
    items.map((item) => loadApmItemMetadata(item, runCommand)),
  );

  return {
    items: results.flatMap((result) => (result.item ? [result.item] : [])),
    warnings: results.flatMap((result) =>
      result.warning ? [result.warning] : [],
    ),
  };
};

const enrichAptssItems = async (
  items: UpdateCenterItem[],
  runCommand: UpdateCenterCommandRunner,
): Promise<UpdateCenterLoadItemsResult> => {
  const results = await Promise.all(
    items.map((item) => loadAptssItemMetadata(item, runCommand)),
  );

  return {
    items: results.flatMap((result) => (result.item ? [result.item] : [])),
    warnings: results.flatMap((result) =>
      result.warning ? [result.warning] : [],
    ),
  };
};

const getStoreArch = (
  item: Pick<UpdateCenterItem, "source" | "arch">,
): string => {
  const arch = item.arch;
  if (!arch) {
    return "";
  }

  if (arch.includes("-")) {
    return arch;
  }

  return `${arch}-${item.source === "aptss" ? "store" : "apm"}`;
};

const loadJson = async <T>(url: string): Promise<T> => {
  const response = await fetch(url);
  if (!response.ok) {
    throw new Error(`Request failed for ${url}`);
  }

  return (await response.json()) as T;
};

const loadStoreCategoryMap = async (
  storeArch: string,
): Promise<StoreAppMetadataMap> => {
  const categories = await loadJson<Record<string, unknown>>(
    `${REMOTE_STORE_BASE_URL}/${storeArch}/categories.json`,
  );
  const categoryEntries = await Promise.allSettled(
    Object.keys(categories).map(async (category) => {
      const apps = await loadJson<RemoteCategoryAppEntry[]>(
        `${REMOTE_STORE_BASE_URL}/${storeArch}/${category}/applist.json`,
      );

      return { apps, category };
    }),
  );

  const categoryMap: StoreAppMetadataMap = new Map();
  for (const entry of categoryEntries) {
    if (entry.status !== "fulfilled") {
      continue;
    }

    for (const app of entry.value.apps) {
      if (app.Pkgname && !categoryMap.has(app.Pkgname)) {
        categoryMap.set(app.Pkgname, {
          category: entry.value.category,
          name: app.Name,
        });
      }
    }
  }

  return categoryMap;
};

const getStoreCategoryMap = (
  storeArch: string,
): Promise<StoreAppMetadataMap> => {
  const cached = categoryCache.get(storeArch);
  if (cached) {
    return cached;
  }

  const pending = loadStoreCategoryMap(storeArch).catch(() => {
    categoryCache.delete(storeArch);
    return new Map();
  });
  categoryCache.set(storeArch, pending);
  return pending;
};

const enrichItemCategories = async (
  items: UpdateCenterItem[],
): Promise<UpdateCenterItem[]> => {
  return await Promise.all(
    items.map(async (item) => {
      if (item.category) {
        return item;
      }

      const storeArch = getStoreArch(item);
      if (!storeArch) {
        return item;
      }

      const categoryMap = await getStoreCategoryMap(storeArch);
      const metadata = categoryMap.get(item.pkgname);
      return metadata
        ? {
            ...item,
            category: metadata.category,
            ...(metadata.name ? { name: metadata.name } : {}),
          }
        : item;
    }),
  );
};

const enrichItemIcons = (items: UpdateCenterItem[]): UpdateCenterItem[] => {
  return items.map((item) => {
    const icons = resolveUpdateItemIcons(item);

    return Object.keys(icons).length > 0 ? { ...item, ...icons } : item;
  });
};

const isSourceEnabled = (
  storeFilter: StoreFilter,
  source: "spark" | "apm",
): boolean => {
  return storeFilter === "both" || storeFilter === source;
};

const isCommandAvailable = async (
  runCommand: UpdateCenterCommandRunner,
  command: "aptss" | "apm",
): Promise<boolean> => {
  const result = await runCommand("which", [command]);
  return result.code === 0 && result.stdout.trim().length > 0;
};

export const loadUpdateCenterItems = async (
  storeFilter: StoreFilter = "both",
  runCommand: UpdateCenterCommandRunner = runCommandCapture,
): Promise<UpdateCenterLoadItemsResult> => {
  console.log(
    `[UpdateCenter] loadUpdateCenterItems called with storeFilter=${storeFilter}`,
  );
  const [sparkEnabled, apmEnabled] = await Promise.all([
    isSourceEnabled(storeFilter, "spark")
      ? isCommandAvailable(runCommand, "aptss")
      : Promise.resolve(false),
    isSourceEnabled(storeFilter, "apm")
      ? isCommandAvailable(runCommand, "apm")
      : Promise.resolve(false),
  ]);
  console.log(
    `[UpdateCenter] sparkEnabled=${sparkEnabled}, apmEnabled=${apmEnabled}`,
  );

  const [aptssResult, apmResult, aptssInstalledResult, apmInstalledResult] =
    await Promise.all([
      sparkEnabled
        ? runCommand(
            APTSS_LIST_UPGRADABLE_COMMAND.command,
            APTSS_LIST_UPGRADABLE_COMMAND.args,
          )
        : Promise.resolve({ code: 0, stdout: "", stderr: "" }),
      apmEnabled
        ? runCommand("apm", ["list", "--upgradable"])
        : Promise.resolve({ code: 0, stdout: "", stderr: "" }),
      sparkEnabled
        ? runCommand(
            DPKG_QUERY_INSTALLED_COMMAND.command,
            DPKG_QUERY_INSTALLED_COMMAND.args,
          )
        : Promise.resolve({ code: 0, stdout: "", stderr: "" }),
      apmEnabled
        ? runCommand("apm", ["list", "--installed"])
        : Promise.resolve({ code: 0, stdout: "", stderr: "" }),
    ]);

  console.log(
    `[UpdateCenter] aptssResult: code=${aptssResult.code}, stdout=${aptssResult.stdout.substring(0, 500)}, stderr=${aptssResult.stderr.substring(0, 500)}`,
  );
  console.log(
    `[UpdateCenter] apmResult: code=${apmResult.code}, stdout=${apmResult.stdout.substring(0, 500)}, stderr=${apmResult.stderr.substring(0, 500)}`,
  );
  console.log(
    `[UpdateCenter] aptssInstalledResult: code=${aptssInstalledResult.code}, stdout=${aptssInstalledResult.stdout.substring(0, 500)}`,
  );
  console.log(
    `[UpdateCenter] apmInstalledResult: code=${apmInstalledResult.code}, stdout=${apmInstalledResult.stdout.substring(0, 500)}`,
  );

  const aptssAvailable =
    sparkEnabled && (aptssResult.code === 0 || aptssInstalledResult.code === 0);

  const warnings = [
    aptssAvailable
      ? getCommandError("aptss upgradable query", aptssResult)
      : null,
    apmEnabled ? getCommandError("apm upgradable query", apmResult) : null,
    aptssAvailable
      ? getCommandError("dpkg installed query", aptssInstalledResult)
      : null,
    apmEnabled
      ? getCommandError("apm installed query", apmInstalledResult)
      : null,
  ].filter((message): message is string => message !== null);

  const aptssItems =
    aptssAvailable && aptssResult.code === 0
      ? parseAptssUpgradableOutput(aptssResult.stdout)
      : [];
  const apmItems =
    apmEnabled && apmResult.code === 0
      ? parseApmUpgradableOutput(apmResult.stdout)
      : [];
  console.log(
    `[UpdateCenter] parsed aptssItems count=${aptssItems.length}`,
    aptssItems.map((i) => `${i.pkgname} ${i.currentVersion}->${i.nextVersion}`),
  );
  console.log(
    `[UpdateCenter] parsed apmItems count=${apmItems.length}`,
    apmItems.map((i) => `${i.pkgname} ${i.currentVersion}->${i.nextVersion}`),
  );

  const installedSources = buildInstalledSourceMap(
    aptssAvailable && aptssInstalledResult.code === 0
      ? aptssInstalledResult.stdout
      : "",
    apmInstalledResult.code === 0 ? apmInstalledResult.stdout : "",
  );
  console.log(`[UpdateCenter] installedSources size=${installedSources.size}`);

  const [categorizedAptssItems, categorizedApmItems] = await Promise.all([
    aptssAvailable ? enrichItemCategories(aptssItems) : Promise.resolve([]),
    apmEnabled ? enrichItemCategories(apmItems) : Promise.resolve([]),
  ]);
  const [enrichedAptssItems, enrichedApmItems] = await Promise.all([
    aptssAvailable
      ? enrichAptssItems(categorizedAptssItems, runCommand)
      : Promise.resolve({ items: [], warnings: [] }),
    apmEnabled
      ? enrichApmItems(categorizedApmItems, runCommand)
      : Promise.resolve({ items: [], warnings: [] }),
  ]);
  console.log(
    `[UpdateCenter] enrichedAptssItems: count=${enrichedAptssItems.items.length}, warnings=${enrichedAptssItems.warnings.length}`,
    enrichedAptssItems.warnings,
  );
  console.log(
    `[UpdateCenter] enrichedApmItems: count=${enrichedApmItems.items.length}, warnings=${enrichedApmItems.warnings.length}`,
    enrichedApmItems.warnings,
  );

  const mergedItems = mergeUpdateSources(
    enrichItemIcons(enrichedAptssItems.items),
    enrichItemIcons(enrichedApmItems.items),
    installedSources,
  );
  console.log(
    `[UpdateCenter] mergedItems count=${mergedItems.length}`,
    mergedItems.map(
      (i) => `${i.pkgname} (${i.source}) ${i.currentVersion}->${i.nextVersion}`,
    ),
  );

  return {
    items: mergedItems,
    warnings: [
      ...warnings,
      ...enrichedAptssItems.warnings,
      ...enrichedApmItems.warnings,
    ],
  };
};

export const registerUpdateCenterIpc = (
  ipc: Pick<typeof ipcMain, "handle">,
  service: Pick<
    UpdateCenterService,
    | "open"
    | "refresh"
    | "ignore"
    | "unignore"
    | "start"
    | "cancel"
    | "getState"
    | "subscribe"
  >,
): void => {
  ipc.handle(
    "update-center-run-system-update",
    async (_event, storeFilter: StoreFilter = "both") => {
      console.log(
        `[UpdateCenter] update-center-run-system-update called with storeFilter=${storeFilter}`,
      );

      const results: { aptss?: string; apm?: string } = {};

      const runCommand = (
        command: string,
        args: string[],
      ): Promise<{ code: number; stdout: string; stderr: string }> =>
        new Promise((resolve) => {
          const child = spawn(command, args, {
            shell: false,
            env: process.env,
          });
          let stdout = "";
          let stderr = "";
          child.stdout?.on("data", (data) => {
            stdout += data.toString();
          });
          child.stderr?.on("data", (data) => {
            stderr += data.toString();
          });
          child.on("error", (err) =>
            resolve({ code: -1, stdout, stderr: err.message }),
          );
          child.on("close", (code) =>
            resolve({ code: code ?? -1, stdout, stderr }),
          );
        });

      const isSourceEnabled = (
        filter: StoreFilter,
        source: "spark" | "apm",
      ): boolean => filter === "both" || filter === source;

      // aptss update — 需要提权
      if (isSourceEnabled(storeFilter, "spark")) {
        const whichResult = await runCommand("which", ["aptss"]);
        const aptssAvailable =
          whichResult.code === 0 && whichResult.stdout.trim().length > 0;
        if (aptssAvailable) {
          console.log(
            "[UpdateCenter] Running: pkexec shell-caller aptss ssupdate",
          );
          const superUserCmd = await findExecutable(
            SUPER_USER_COMMAND_CANDIDATES[0],
          );
          if (superUserCmd) {
            const result = await runCommand(superUserCmd, [
              SHELL_CALLER_PATH,
              "aptss",
              "ssupdate",
            ]);
            results.aptss =
              result.code === 0
                ? "ok"
                : `failed: ${result.stderr.substring(0, 200)}`;
            console.log("[UpdateCenter] aptss ssupdate result:", results.aptss);
          } else {
            results.aptss = "failed: pkexec not found";
            console.warn(
              "[UpdateCenter] pkexec not found, skipping aptss update",
            );
          }
        } else {
          results.aptss = "skipped: aptss not installed";
        }
      }

      // apm update — 也需要提权
      if (isSourceEnabled(storeFilter, "apm")) {
        const whichResult = await runCommand("which", ["apm"]);
        const apmAvailable =
          whichResult.code === 0 && whichResult.stdout.trim().length > 0;
        if (apmAvailable) {
          console.log("[UpdateCenter] Running: pkexec shell-caller apm update");
          const superUserCmd = await findExecutable(
            SUPER_USER_COMMAND_CANDIDATES[0],
          );
          if (superUserCmd) {
            const result = await runCommand(superUserCmd, [
              SHELL_CALLER_PATH,
              "apm",
              "update",
            ]);
            results.apm =
              result.code === 0
                ? "ok"
                : `failed: ${result.stderr.substring(0, 200)}`;
            console.log("[UpdateCenter] apm update result:", results.apm);
          } else {
            results.apm = "failed: pkexec not found";
            console.warn(
              "[UpdateCenter] pkexec not found, skipping apm update",
            );
          }
        } else {
          results.apm = "skipped: apm not installed";
        }
      }

      return results;
    },
  );

  ipc.handle(
    "update-center-open",
    (_event, storeFilter: StoreFilter = "both") => service.open(storeFilter),
  );
  ipc.handle(
    "update-center-refresh",
    (_event, storeFilter: StoreFilter = "both") => service.refresh(storeFilter),
  );
  ipc.handle(
    "update-center-ignore",
    (_event, payload: UpdateCenterIgnorePayload) => service.ignore(payload),
  );
  ipc.handle(
    "update-center-unignore",
    (_event, payload: UpdateCenterIgnorePayload) => service.unignore(payload),
  );
  ipc.handle("update-center-start", (_event, tasks: UpdateCenterStartTask[]) =>
    service.start(tasks),
  );
  ipc.handle("update-center-cancel", (_event, taskKey: string) =>
    service.cancel(taskKey),
  );
  ipc.handle("update-center-get-state", () => service.getState());

  service.subscribe((snapshot) => {
    for (const win of BrowserWindow.getAllWindows()) {
      win.webContents.send("update-center-state", snapshot);
    }
  });
};

let updateCenterService: UpdateCenterService | null = null;

export const initializeUpdateCenter = (): UpdateCenterService => {
  if (updateCenterService) {
    return updateCenterService;
  }

  updateCenterService = createUpdateCenterService({
    loadItems: loadUpdateCenterItems,
  });
  registerUpdateCenterIpc(ipcMain, updateCenterService);

  return updateCenterService;
};

export { createUpdateCenterService } from "./service";
