import { execFile } from "node:child_process";
import {
  mkdir,
  readFile,
  readdir,
  stat,
  writeFile,
} from "node:fs/promises";
import { extname, resolve } from "node:path";
import { promisify } from "node:util";

const execFileAsync = promisify(execFile);
const projectRoot = resolve(import.meta.dirname, "..");
const snapshotPath = resolve(projectRoot, "lib/feishu-source-snapshot.json");
const downloadDir = "/private/tmp/koc-feishu-images-954953";
const uploadOriginArg = process.argv.find((item) =>
  item.startsWith("--upload-origin="),
);
const uploadOrigin = uploadOriginArg
  ? uploadOriginArg.slice("--upload-origin=".length).replace(/\/$/, "")
  : "";
const internalToken =
  process.env.KOC_ADMIN_INTERNAL_TOKEN ??
  process.env.ADMIN_INTERNAL_TOKEN ??
  "";
const reuseDownloads = process.argv.includes("--reuse-downloads");
const fromRowArg = process.argv.find((item) => item.startsWith("--from-row="));
const fromRow = fromRowArg
  ? Math.max(1, Number(fromRowArg.slice("--from-row=".length)) || 1)
  : 1;

if (uploadOrigin && !internalToken) {
  throw new Error(
    "KOC_ADMIN_INTERNAL_TOKEN is required when --upload-origin is provided",
  );
}

const snapshot = JSON.parse(await readFile(snapshotPath, "utf8"));
const sourceUrl =
  "https://eodzc79n5l.feishu.cn/wiki/BSzxwRbGJi5dWoksauicUjtonks?from=from_copylink&sheet=954953";
const quietEnv = {
  ...process.env,
  LARKSUITE_CLI_NO_UPDATE_NOTIFIER: "1",
  LARKSUITE_CLI_NO_SKILLS_NOTIFIER: "1",
};

await mkdir(downloadDir, { recursive: true });

const { stdout: cellsStdout } = await execFileAsync(
  "lark-cli",
  [
    "sheets",
    "+cells-get",
    "--url",
    sourceUrl,
    "--sheet-id",
    snapshot.sheetId,
    "--range",
    "F2:H62",
    "--include",
    "value",
    "--max-chars",
    "500000",
    "--format",
    "json",
  ],
  {
    cwd: projectRoot,
    env: quietEnv,
    maxBuffer: 2_000_000,
  },
);

const cellsPayload = JSON.parse(cellsStdout);
if (!cellsPayload.ok || cellsPayload.data?.has_more) {
  throw new Error("Feishu image cells were not returned completely");
}
const range = cellsPayload.data?.ranges?.[0];
if (!range || range.truncated || range.actual_range !== "F2:H62") {
  throw new Error(
    `Unexpected Feishu image range: ${range?.actual_range ?? "missing"}`,
  );
}

const sourceRows = new Map(
  snapshot.rows.map((row) => [Number(row.sourceRow), row]),
);
const assets = [];

for (let rowIndex = 0; rowIndex < range.cells.length; rowIndex += 1) {
  const sheetRow = Number(range.row_indices[rowIndex]);
  const sourceRow = sheetRow - 1;
  if (!sourceRows.has(sourceRow)) continue;
  const cells = range.cells[rowIndex] ?? [];
  for (let columnIndex = 0; columnIndex < cells.length; columnIndex += 1) {
    const column = range.col_indices[columnIndex];
    const imageIndex = ["F", "G", "H"].indexOf(column) + 1;
    if (imageIndex < 1) continue;
    const richText = cells[columnIndex]?.rich_text ?? [];
    const image = richText.find((item) => item.type === "embed-image");
    if (!image?.image_token) continue;
    assets.push({
      sourceRow,
      imageIndex,
      token: image.image_token,
      width: Number(image.image_width) || null,
      height: Number(image.image_height) || null,
      key: `content-assets/${snapshot.sheetId}/${sourceRow}/${imageIndex}`,
    });
  }
}

async function downloadAsset(asset) {
  const baseName = `row-${asset.sourceRow}-image-${asset.imageIndex}`;
  if (reuseDownloads) {
    const existingName = (await readdir(downloadDir)).find((name) =>
      name.startsWith(`${baseName}.`),
    );
    if (existingName) {
      const localPath = resolve(downloadDir, existingName);
      const extension = extname(existingName).toLowerCase();
      const fileInfo = await stat(localPath);
      return {
        ...asset,
        localPath,
        contentType:
          extension === ".png"
            ? "image/png"
            : extension === ".webp"
              ? "image/webp"
              : "image/jpeg",
        sizeBytes: fileInfo.size,
      };
    }
  }
  const { stdout } = await execFileAsync(
    "lark-cli",
    [
      "docs",
      "+media-download",
      "--token",
      asset.token,
      "--output",
      `./${baseName}`,
      "--overwrite",
    ],
    {
      cwd: downloadDir,
      env: quietEnv,
      maxBuffer: 200_000,
    },
  );
  const result = JSON.parse(stdout.slice(stdout.indexOf("{")));
  if (!result.ok || !result.data?.saved_path) {
    throw new Error(
      `Failed to download row ${asset.sourceRow} image ${asset.imageIndex}`,
    );
  }
  return {
    ...asset,
    localPath: result.data.saved_path,
    contentType: result.data.content_type || "application/octet-stream",
    sizeBytes: Number(result.data.size_bytes) || 0,
  };
}

const downloaded = [];
const queue = [...assets];
const workers = Array.from({ length: 4 }, async () => {
  while (queue.length > 0) {
    const asset = queue.shift();
    if (!asset) return;
    downloaded.push(await downloadAsset(asset));
  }
});
await Promise.all(workers);
downloaded.sort(
  (left, right) =>
    left.sourceRow - right.sourceRow || left.imageIndex - right.imageIndex,
);

for (const row of snapshot.rows) {
  row.images = downloaded
    .filter((asset) => asset.sourceRow === Number(row.sourceRow))
    .map((asset) => ({
      index: asset.imageIndex,
      key: asset.key,
      width: asset.width,
      height: asset.height,
    }));
}
await writeFile(snapshotPath, `${JSON.stringify(snapshot, null, 2)}\n`, "utf8");

if (uploadOrigin) {
  const sharp = (await import("sharp")).default;
  const uploadConcurrency = new URL(uploadOrigin).hostname === "localhost" ? 4 : 1;

  async function prepareUpload(asset) {
    const original = await readFile(asset.localPath);
    if (original.byteLength < 800_000) {
      return {
        bytes: original,
        contentType: asset.contentType,
        extension: extname(asset.localPath) || ".bin",
      };
    }
    let quality = 86;
    let compressed = await sharp(original)
      .rotate()
      .flatten({ background: "#ffffff" })
      .resize({
        width: 1800,
        height: 2200,
        fit: "inside",
        withoutEnlargement: true,
      })
      .jpeg({ quality, mozjpeg: true })
      .toBuffer();
    while (compressed.byteLength > 800_000 && quality > 58) {
      quality -= 7;
      compressed = await sharp(original)
        .rotate()
        .flatten({ background: "#ffffff" })
        .resize({
          width: 1600,
          height: 2000,
          fit: "inside",
          withoutEnlargement: true,
        })
        .jpeg({ quality, mozjpeg: true })
        .toBuffer();
    }
    return {
      bytes: compressed,
      contentType: "image/jpeg",
      extension: ".jpg",
    };
  }

  const uploadQueue = downloaded.filter((asset) => asset.sourceRow >= fromRow);
  const uploadWorkers = Array.from({ length: uploadConcurrency }, async () => {
    while (uploadQueue.length > 0) {
      const asset = uploadQueue.shift();
      if (!asset) return;
      const prepared = await prepareUpload(asset);
      let uploaded = false;
      let lastError = "unknown error";
      for (let attempt = 1; attempt <= 3; attempt += 1) {
        const form = new FormData();
        form.append("sheetId", snapshot.sheetId);
        form.append("sourceRow", String(asset.sourceRow));
        form.append("imageIndex", String(asset.imageIndex));
        form.append(
          "file",
          new File(
            [prepared.bytes],
            `image-${asset.imageIndex}${prepared.extension}`,
            {
              type: prepared.contentType,
            },
          ),
        );
        const response = await fetch(
          `${uploadOrigin}/api/content-image-upload`,
          {
            method: "POST",
            headers: {
              "X-KOC-Admin-Token": internalToken,
            },
            body: form,
          },
        );
        const responseText = await response.text();
        let result = {};
        try {
          result = JSON.parse(responseText);
        } catch {
          result = { error: responseText || `HTTP ${response.status}` };
        }
        if (response.ok) {
          uploaded = true;
          break;
        }
        lastError = result.error ?? `HTTP ${response.status}`;
        if (response.status < 500 && response.status !== 404) break;
        await new Promise((resolveDelay) =>
          setTimeout(resolveDelay, attempt * 750),
        );
      }
      if (!uploaded) {
        throw new Error(
          `Upload failed for row ${asset.sourceRow} image ${asset.imageIndex}: ${lastError}`,
        );
      }
    }
  });
  await Promise.all(uploadWorkers);
}

const totalBytes = downloaded.reduce(
  (sum, asset) => sum + asset.sizeBytes,
  0,
);
console.log(
  JSON.stringify({
    rows: snapshot.rows.length,
    images: downloaded.length,
    totalBytes,
    uploaded: Boolean(uploadOrigin),
    uploadedImages: uploadOrigin
      ? downloaded.filter((asset) => asset.sourceRow >= fromRow).length
      : 0,
    downloadDir,
  }),
);
