// .gitea/scripts/getLatestRunWithArtifacts.js
// Purpose: Find latest successful run that exposes all REQUIRED_ARTIFACTS via GUI URLs.
// Strategy:
//   1. Try to list runs via API (/actions/runs).
//   2. If not available (404), fallback to scraping HTML /actions page.
//   3. For each run (newest first, only "success"), check artifacts by probing GUI URLs.
// Outputs: sets `run_id` to GITHUB_OUTPUT and writes .gitea/.cache/run_id file.

const fs = require("fs");
const path = require("path");

const BASE = process.env.GITEA_BASE_URL;
const OWNER = process.env.OWNER;
const REPO  = process.env.REPO;
const TOKEN = process.env.GITEA_PAT;
const SCAN_LIMIT = Number(process.env.SCAN_LIMIT || "100");
const REQUIRED_ARTIFACTS = (process.env.REQUIRED_ARTIFACTS || "frontend,webapi")
    .split(",").map(s => s.trim()).filter(Boolean);

if (!BASE || !OWNER || !REPO) {
    console.error("Missing one of: GITEA_BASE_URL, OWNER, REPO");
    process.exit(1);
}
if (!TOKEN) {
    console.error("Missing GITEA_PAT");
    process.exit(1);
}

const cacheDir = path.join(".gitea", ".cache");
fs.mkdirSync(cacheDir, { recursive: true });

async function http(url, opts = {}) {
    return fetch(url, {
        ...opts,
        headers: { Authorization: `token ${TOKEN}`, ...(opts.headers || {}) },
    });
}

async function apiJSON(url) {
    const res = await http(url);
    if (!res.ok) {
        const text = await res.text().catch(() => "");
        const err = new Error(`API ${res.status} ${res.statusText} for ${url}\n${text}`);
        err.status = res.status;
        throw err;
    }
    return res.json();
}

// ---- Run listing ----
function normalizeRunList(resp) {
    if (Array.isArray(resp)) return resp;
    return resp?.runs || resp?.workflow_runs || resp?.data || resp?.items || [];
}

async function tryApiListRuns() {
    const url = `${BASE}/api/v1/repos/${OWNER}/${REPO}/actions/runs?limit=${SCAN_LIMIT}`;
    try {
        const resp = await apiJSON(url);
        return normalizeRunList(resp);
    } catch (e) {
        if (e.status === 404) return null;
        throw e;
    }
}

async function listRunsFromHtml() {
    const url = `${BASE}/${OWNER}/${REPO}/actions`;
    const res = await http(url, { headers: { Accept: "text/html" } });
    if (!res.ok) {
        const t = await res.text().catch(() => "");
        throw new Error(`HTML ${res.status} for ${url}\n${t}`);
    }
    const html = await res.text();
    const runIds = Array.from(html.matchAll(/\/actions\/runs\/(\d+)/g))
        .map(m => Number(m[1]))
        .filter(n => Number.isFinite(n));
    const unique = [...new Set(runIds)].sort((a, b) => b - a);
    return unique.slice(0, SCAN_LIMIT).map(id => ({ id, status: "success" })); // HTML doesn’t give status, assume ok
}

// ---- Artifact check via GUI URL ----
async function headOk(url) {
    let res = await http(url, { method: "HEAD", redirect: "follow" });
    if (res.ok) return true;
    res = await http(url, { method: "GET", redirect: "manual" });
    return res.status >= 200 && res.status < 400;
}

(async () => {
    let runs = await tryApiListRuns();
    if (!runs) {
        console.log("Runs API not available on this Gitea – falling back to HTML scraping.");
        runs = await listRunsFromHtml();
    }

    if (!runs.length) {
        console.error("No workflow runs found.");
        process.exit(1);
    }

    // newest first
    const candidates = runs
        .filter(r => r && r.id != null)
        .sort((a, b) => (b.id ?? 0) - (a.id ?? 0))
        .filter(r => (r.status || "").toLowerCase() === "success" || !r.status); // HTML case: no status info

    if (!candidates.length) {
        console.error("No successful runs found.");
        process.exit(1);
    }

    console.log(`Scanning ${candidates.length} runs for artifacts: ${REQUIRED_ARTIFACTS.join(", ")}`);

    let picked = null;
    for (const r of candidates) {
        const runId = r.id;
        const urls = REQUIRED_ARTIFACTS.map(
            name => `${BASE}/${OWNER}/${REPO}/actions/runs/${runId}/artifacts/${encodeURIComponent(name)}`
        );

        let allPresent = true;
        for (const u of urls) {
            const ok = await headOk(u).catch(() => false);
            if (!ok) {
                allPresent = false;
                console.log(`Run ${runId}: artifact not accessible -> ${u}`);
                break;
            }
        }

        if (allPresent) {
            picked = { id: runId };
            console.log(`Picked run_id=${runId}`);
            break;
        }
    }

    if (!picked) {
        console.error("No run exposes all required artifacts.");
        process.exit(1);
    }

    const runIdStr = String(picked.id);
    fs.writeFileSync(path.join(cacheDir, "run_id"), runIdStr, "utf8");

    if (process.env.GITHUB_OUTPUT) {
        fs.appendFileSync(process.env.GITHUB_OUTPUT, `run_id=${runIdStr}\n`);
    } else {
        console.log(`::set-output name=run_id::${runIdStr}`);
    }
})().catch(err => {
    console.error(err.stack || err.message || String(err));
    process.exit(1);
});