"use strict";
// 把 dist/ 的產物發佈到 Gitea 的 Releases。
//
//   set GITEA_TOKEN=<token>      （PowerShell：$env:GITEA_TOKEN="<token>"）
//   npm run release              實際發佈
//   npm run release -- --dry-run 只檢查，不做任何寫入
//
// token 需要 write:repository 權限。owner/repo 由 git remote 推導，
// 網址預設為 https://<remote 主機>；SSH 埠與網頁埠不同時用 GITEA_URL 覆寫。

const fs = require("node:fs");
const http = require("node:http");
const https = require("node:https");
const path = require("node:path");
const { execFileSync } = require("node:child_process");

const APP_DIR = path.resolve(__dirname, "..");
const DIST_DIR = path.join(APP_DIR, "dist");
const DRY_RUN = process.argv.includes("--dry-run");
const TOKEN = process.env.GITEA_TOKEN || "";

const pkg = JSON.parse(fs.readFileSync(path.join(APP_DIR, "package.json"), "utf8"));
const TAG = `v${pkg.version}`;

// 要上傳的產物；.blockmap 只有 electron-updater 的差分更新才需要，目前沒用到
const ASSET_PATTERNS = [/\.exe$/i, /\.zip$/i];

function git(...args) {
  // stderr 丟掉：releaseNotes 會刻意去 describe 一個可能不存在的舊 tag，
  // 讓 git 的 fatal 訊息印出來會被誤認成發佈失敗
  return execFileSync("git", args, { cwd: APP_DIR, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
}

function remoteInfo() {
  const url = git("remote", "get-url", "origin");
  const m = url.match(/^(?:ssh:\/\/(?:[^@]+@)?|(?:https?:\/\/)(?:[^@]+@)?|(?:[^@]+@))([^:/]+)(?::\d+)?[:/](.+?)\/([^/]+?)(?:\.git)?$/);
  if (!m) throw new Error(`看不懂 remote 網址：${url}`);
  return { host: m[1], owner: m[2], repo: m[3] };
}

// stream 模式：先送 head，再串流檔案，最後以 tail 收尾。
// 產物上百 MB，不能整份讀進記憶體。
function request(method, url, { headers = {}, body = null, head = null, tail = null, stream = null, size = 0 } = {}) {
  return new Promise((resolve, reject) => {
    const u = new URL(url);
    const lib = u.protocol === "https:" ? https : http;
    const req = lib.request(u, { method, headers }, (res) => {
      let text = "";
      res.setEncoding("utf8");
      res.on("data", (c) => { text += c; });
      res.on("end", () => {
        let json = null;
        try { json = text ? JSON.parse(text) : null; } catch { /* 非 JSON 就留 null */ }
        resolve({ status: res.statusCode, json, text });
      });
    });
    req.on("error", reject);
    if (!stream) {
      req.end(body);
      return;
    }
    req.write(head);
    let sent = 0;
    let lastPct = -1;
    stream.on("data", (chunk) => {
      sent += chunk.length;
      const pct = Math.floor((sent / size) * 100);
      if (pct >= lastPct + 10) { lastPct = pct; process.stdout.write(`\r    上傳中 ${pct}%   `); }
    });
    stream.on("error", reject);
    stream.pipe(req, { end: false });
    stream.on("end", () => req.end(tail));
  });
}

function apiUrl(base, owner, repo, suffix) {
  return `${base}/api/v1/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/${suffix}`;
}

async function api(method, url, payload) {
  const headers = { Accept: "application/json" };
  if (TOKEN) headers.Authorization = `token ${TOKEN}`;
  let body = null;
  if (payload !== undefined) {
    body = Buffer.from(JSON.stringify(payload), "utf8");
    headers["Content-Type"] = "application/json";
    headers["Content-Length"] = body.length;
  }
  return request(method, url, { headers, body });
}

async function uploadAsset(base, owner, repo, releaseId, filePath) {
  const name = path.basename(filePath);
  const size = fs.statSync(filePath).size;
  const boundary = `----claudepet${Date.now().toString(16)}`;
  const head = Buffer.from(
    `--${boundary}\r\n` +
    `Content-Disposition: form-data; name="attachment"; filename="${name}"\r\n` +
    "Content-Type: application/octet-stream\r\n\r\n",
    "utf8",
  );
  const tail = Buffer.from(`\r\n--${boundary}--\r\n`, "utf8");
  const url = `${apiUrl(base, owner, repo, `releases/${releaseId}/assets`)}?name=${encodeURIComponent(name)}`;
  const res = await request("POST", url, {
    headers: {
      Authorization: `token ${TOKEN}`,
      Accept: "application/json",
      "Content-Type": `multipart/form-data; boundary=${boundary}`,
      "Content-Length": head.length + size + tail.length,
    },
    head,
    tail,
    stream: fs.createReadStream(filePath),
    size,
  });
  process.stdout.write("\r");
  return res;
}

function releaseNotes() {
  let range = "";
  try {
    const prev = git("describe", "--tags", "--abbrev=0", `${TAG}^`);
    range = `${prev}..HEAD`;
  } catch {
    try { git("rev-parse", "HEAD"); range = "HEAD"; } catch { range = ""; }
  }
  try {
    const log = range === "HEAD"
      ? git("log", "-n", "30", "--pretty=- %s")   // 沒有舊 tag（第一次發佈）就取最近 30 筆
      : git("log", range, "--pretty=- %s");
    return log || `Claude Pet ${TAG}`;
  } catch {
    return `Claude Pet ${TAG}`;
  }
}

async function main() {
  const { host, owner, repo } = remoteInfo();
  const base = (process.env.GITEA_URL || `https://${host}`).replace(/\/+$/, "");

  console.log(`Gitea   : ${base}`);
  console.log(`Repo    : ${owner}/${repo}`);
  console.log(`Tag     : ${TAG}`);

  if (!fs.existsSync(DIST_DIR)) throw new Error(`找不到 ${DIST_DIR}，先執行 npm run dist`);
  // dist/ 可能留著前幾版的產物，只挑檔名含當前版本的，免得舊檔混進新 release
  const files = fs.readdirSync(DIST_DIR)
    .filter((f) => ASSET_PATTERNS.some((re) => re.test(f)) && f.includes(pkg.version))
    .map((f) => path.join(DIST_DIR, f))
    .filter((f) => fs.statSync(f).isFile());
  if (!files.length) throw new Error(`${DIST_DIR} 裡沒有 .exe / .zip，先執行 npm run dist`);

  console.log("產物    :");
  for (const f of files) console.log(`  ${path.basename(f)}  ${(fs.statSync(f).size / 1048576).toFixed(1)} MB`);

  // 附件限制：超過就先講清楚，不要傳到一半才失敗
  const settings = await request("GET", `${base}/api/v1/settings/attachment`, { headers: { Accept: "application/json" } });
  if (settings.json) {
    const maxMB = Number(settings.json.max_size);
    const types = String(settings.json.allowed_types || "");
    const anyType = types.includes("*/*") || types.trim() === "";
    const problems = [];
    for (const f of files) {
      const sizeMB = fs.statSync(f).size / 1048576;
      if (maxMB && sizeMB > maxMB) problems.push(`${path.basename(f)} 為 ${sizeMB.toFixed(1)} MB，超過上限 ${maxMB} MB`);
      const ext = path.extname(f).toLowerCase();
      if (!anyType && !types.split(",").map((t) => t.trim().toLowerCase()).includes(ext)) {
        problems.push(`${path.basename(f)} 的副檔名 ${ext} 不在 allowed_types 內`);
      }
    }
    if (problems.length) {
      console.error("\n伺服器的附件設定擋住這次發佈：");
      for (const p of problems) console.error(`  - ${p}`);
      console.error("\n請在 Gitea 的 app.ini 調整後重啟：");
      console.error("  [attachment]\n  MAX_SIZE = 300\n  ALLOWED_TYPES = */*");
      process.exitCode = 1;
      return;
    }
  }

  const existing = await api("GET", apiUrl(base, owner, repo, `releases/tags/${encodeURIComponent(TAG)}`));
  if (DRY_RUN) {
    console.log(`\n[dry-run] release ${TAG} ${existing.status === 200 ? "已存在，會重用並覆蓋同名附件" : "不存在，會新建"}`);
    console.log("[dry-run] 變更說明：");
    console.log(releaseNotes().split("\n").map((l) => `  ${l}`).join("\n"));
    console.log("\n[dry-run] 沒有做任何寫入。");
    return;
  }

  if (!TOKEN) throw new Error("缺少 GITEA_TOKEN 環境變數（需要 write:repository 權限）");

  let release = existing.json;
  if (existing.status !== 200) {
    const created = await api("POST", apiUrl(base, owner, repo, "releases"), {
      tag_name: TAG,
      target_commitish: git("rev-parse", "--abbrev-ref", "HEAD"),
      name: `Claude Pet ${TAG}`,
      body: releaseNotes(),
      draft: false,
      prerelease: false,
    });
    if (created.status !== 201) throw new Error(`建立 release 失敗（HTTP ${created.status}）：${created.text}`);
    release = created.json;
    console.log(`\n已建立 release ${TAG}`);
  } else {
    console.log(`\nrelease ${TAG} 已存在，重用`);
  }

  for (const f of files) {
    const name = path.basename(f);
    const dup = (release.assets || []).find((a) => a.name === name);
    if (dup) {
      const del = await api("DELETE", apiUrl(base, owner, repo, `releases/${release.id}/assets/${dup.id}`));
      console.log(`  移除舊的 ${name}（HTTP ${del.status}）`);
    }
    console.log(`  上傳 ${name}`);
    const res = await uploadAsset(base, owner, repo, release.id, f);
    if (res.status !== 201) throw new Error(`上傳 ${name} 失敗（HTTP ${res.status}）：${res.text}`);
    console.log(`  完成 ${name}`);
  }

  console.log(`\n發佈完成：${base}/${owner}/${repo}/releases/tag/${TAG}`);
}

main().catch((err) => {
  console.error(err.message || err);
  process.exitCode = 1;
});
