#!/usr/bin/env node
// Публикует Tauri-релиз в Gitea напрямую через Release API — вместо
// tauri-apps/tauri-action, который жёстко ходит в api.github.com (не
// настраиваемый эндпоинт, для Gitea не годится). Используется только в
// .gitea/workflows/release.yml, job "publish".
//
// Схема:
// 1. Job "build" (матрица платформ) собирает бандлы через `tauri build`
//    напрямую (не через tauri-action) и складывает каждую подписанную пару
//    <файл>+<файл>.sig в dist-bundle/, которая грузится как build-артефакт
//    с именем bundle-<updater_key> (updater_key — "darwin-aarch64" и т.п.,
//    см. matrix в workflow).
// 2. Этот скрипт (job "publish") скачивает все артефакты, находит пары
//    <файл>+.sig, собирает latest.json (формат манифеста апдейтера Tauri v2,
//    см. https://v2.tauri.app/plugin/updater/) и публикует:
//    - версионный релиз (тег = версия, для истории/changelog в Gitea UI);
//    - релиз "latest" — Gitea, в отличие от GitHub, не имеет built-in алиаса
//      /releases/latest/download/<file>, поэтому эмулируем его вручную:
//      тег "latest" удаляется и пересоздаётся на каждом релизе с теми же
//      ассетами, что и версионный — так у апдейтера в tauri.conf.json
//      стабильный URL, не привязанный к конкретной версии.
//
// Оба релиза удаляются-и-пересоздаются при каждом запуске (idempotent —
// повторный прогон того же тега не падает на "уже существует").

import { readFileSync, readdirSync, writeFileSync } from "node:fs";
import { join, basename } from "node:path";

const GITEA_SERVER_URL = requireEnv("GITEA_SERVER_URL").replace(/\/$/, "");
const GITEA_TOKEN = requireEnv("GITEA_TOKEN");
const REPO = requireEnv("GITEA_REPO"); // "owner/name"
const VERSION = requireEnv("RELEASE_VERSION"); // напр. "v1.2.3"
const ARTIFACTS_DIR = process.argv[2] || "artifacts";

function requireEnv(name) {
  const v = process.env[name];
  if (!v) throw new Error(`Переменная окружения ${name} обязательна`);
  return v;
}

function apiUrl(path) {
  return `${GITEA_SERVER_URL}/api/v1/repos/${REPO}${path}`;
}

async function giteaFetch(path, options = {}) {
  const res = await fetch(apiUrl(path), {
    ...options,
    headers: {
      Authorization: `token ${GITEA_TOKEN}`,
      ...(options.headers || {}),
    },
  });
  if (!res.ok && res.status !== 404) {
    const text = await res.text().catch(() => "");
    throw new Error(`Gitea API ${path} -> ${res.status}: ${text}`);
  }
  return res;
}

/** Рекурсивно находит все пары <файл>+<файл>.sig под dir. */
function collectSigPairs(dir) {
  const pairs = [];
  for (const entry of readdirSync(dir, { withFileTypes: true })) {
    const full = join(dir, entry.name);
    if (entry.isDirectory()) {
      pairs.push(...collectSigPairs(full));
      continue;
    }
    if (entry.name.endsWith(".sig")) {
      pairs.push({ sigPath: full, artifactPath: full.slice(0, -4), dir });
    }
  }
  return pairs;
}

/** Удаляет (если есть) и заново создаёт релиз на теге — гарантирует idempotent-повтор. */
async function ensureRelease(tag) {
  const existing = await giteaFetch(`/releases/tags/${tag}`);
  if (existing.status === 200) {
    const { id } = await existing.json();
    await giteaFetch(`/releases/${id}`, { method: "DELETE" });
  }
  // Релиз и git-тег в Gitea — разные сущности; удаление релиза не удаляет тег.
  await giteaFetch(`/tags/${tag}`, { method: "DELETE" });

  const created = await giteaFetch(`/releases`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      tag_name: tag,
      name: tag,
      draft: false,
      prerelease: false,
      target_commitish: process.env.GITHUB_SHA || "main",
    }),
  });
  if (!created.ok) {
    throw new Error(`Не удалось создать релиз ${tag}: HTTP ${created.status}`);
  }
  return created.json();
}

async function uploadAsset(releaseId, filePath, fileName) {
  const data = readFileSync(filePath);
  const form = new FormData();
  form.append("attachment", new Blob([data]), fileName);
  const res = await giteaFetch(
    `/releases/${releaseId}/assets?name=${encodeURIComponent(fileName)}`,
    { method: "POST", body: form },
  );
  if (!res.ok) {
    throw new Error(`Не удалось загрузить ${fileName}: HTTP ${res.status}`);
  }
  return res.json();
}

async function main() {
  const pairs = collectSigPairs(ARTIFACTS_DIR);
  if (pairs.length === 0) {
    throw new Error(
      `Не найдено ни одной пары <файл>+.sig в ${ARTIFACTS_DIR} — сборка не создала подписанные updater-бандлы?`,
    );
  }

  const versionedRelease = await ensureRelease(VERSION);
  const latestRelease = await ensureRelease("latest");

  const platforms = {};

  for (const { sigPath, artifactPath, dir } of pairs) {
    const signature = readFileSync(sigPath, "utf-8").trim();
    const fileName = basename(artifactPath);
    // Папка артефакта matrix-job'а называется bundle-<updater_key>, см. workflow.
    const updaterKey = basename(dir).replace(/^bundle-/, "");

    console.log(`→ ${updaterKey}: ${fileName}`);

    await uploadAsset(versionedRelease.id, artifactPath, fileName);
    await uploadAsset(latestRelease.id, artifactPath, fileName);

    platforms[updaterKey] = {
      signature,
      // Всегда через тег "latest" — стабильный URL независимо от версии.
      url: `${GITEA_SERVER_URL}/${REPO}/releases/download/latest/${fileName}`,
    };
  }

  const manifest = {
    version: VERSION.replace(/^v/, ""),
    notes: `Netrunner VPN ${VERSION}`,
    pub_date: new Date().toISOString(),
    platforms,
  };

  const manifestPath = join(ARTIFACTS_DIR, "latest.json");
  writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));

  await uploadAsset(versionedRelease.id, manifestPath, "latest.json");
  await uploadAsset(latestRelease.id, manifestPath, "latest.json");

  console.log("✅ Опубликовано:\n" + JSON.stringify(manifest, null, 2));
}

main().catch((e) => {
  console.error(e);
  process.exit(1);
});
