"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.releaseSpeculosDeviceCI = exports.createSpeculosDeviceCI = exports.waitForSpeculosReady = void 0;
const axios_1 = __importDefault(require("axios"));
const speculos_transport_1 = require("@ledgerhq/speculos-transport");
const https_1 = __importDefault(require("https"));
const { GITHUB_TOKEN, SPECULOS_IMAGE_TAG } = process.env;
const GIT_API_URL = "https://api.github.com/repos/LedgerHQ/actions/actions/";
const START_WORKFLOW_ID = "workflows/161487603/dispatches";
const STOP_WORKFLOW_ID = "workflows/161487604/dispatches";
const GITHUB_REF = "main";
const getSpeculosAddress = (runId) => `https://${runId}.speculos.aws.stg.ldg-tech.com`;
const speculosPort = 443;
function uniqueId() {
    const timestamp = Date.now().toString(36);
    const randomString = Math.random().toString(36).slice(2, 7);
    return timestamp + randomString;
}
function slugify(name) {
    return name
        .toLowerCase()
        .trim()
        .replace(/[^a-z0-9]+/g, "-")
        .replace(/^-+|-+$/g, "");
}
/**
 * Helper function to make API requests with error handling
 */
async function githubApiRequest({ method = "POST", urlSuffix, data, params, }) {
    const url = `${GIT_API_URL}${urlSuffix}`;
    try {
        const response = await (0, axios_1.default)({
            method,
            url,
            headers: {
                Authorization: `Bearer ${GITHUB_TOKEN}`,
                Accept: "application/vnd.github+json",
                "X-GitHub-Api-Version": "2022-11-28",
            },
            data,
            params,
        });
        return response.data;
    }
    catch (error) {
        console.warn(`API Request failed: ${method} ${url}`, axios_1.default.isAxiosError(error) ? error.response?.data : error.message);
        throw error;
    }
}
function waitForSpeculosReady(deviceId, { interval = 2_000, timeout = 150_000 } = {}) {
    return new Promise((resolve, reject) => {
        const startTime = Date.now();
        let currentRequest = null;
        const url = getSpeculosAddress(deviceId);
        function cleanup() {
            if (currentRequest) {
                currentRequest.destroy();
                currentRequest = null;
            }
        }
        function check() {
            cleanup();
            currentRequest = https_1.default.get(url, { timeout: 10000 }, res => {
                if (res.statusCode && res.statusCode >= 200 && res.statusCode < 400) {
                    process.env.SPECULOS_ADDRESS = url;
                    cleanup();
                    console.warn(`Speculos is ready at ${url}`);
                    resolve(true);
                }
                else {
                    console.warn(`Speculos not ready yet, status: ${res.statusCode}`);
                    retry();
                }
            });
            currentRequest.on("error", error => {
                console.error(`Request error: ${error.message}`);
                retry();
            });
            currentRequest.on("timeout", () => {
                console.error("Request timeout");
                retry();
            });
        }
        function retry() {
            if (Date.now() - startTime >= timeout) {
                cleanup();
                reject(new Error(`Timeout: ${url} did not become available within ${timeout}ms`));
            }
            else {
                setTimeout(check, interval);
            }
        }
        check();
    });
}
exports.waitForSpeculosReady = waitForSpeculosReady;
function createStartPayload(deviceParams, runId) {
    const { model, firmware, appName, appVersion, dependency, dependencies } = deviceParams;
    let additional_args = "-p";
    if (dependency) {
        additional_args = `${additional_args} -l ${dependency}:/apps/${(0, speculos_transport_1.conventionalAppSubpath)(model, firmware, dependency, appVersion)}`;
    }
    else if (dependencies) {
        additional_args = [
            ...new Set(dependencies.map(dep => `${additional_args} -l ${dep.name}:/apps/${(0, speculos_transport_1.conventionalAppSubpath)(model, firmware, dep.name, dep.appVersion ?? "1.0.0")}`)),
        ].join(" ");
    }
    return {
        ref: GITHUB_REF,
        inputs: {
            speculos_version: SPECULOS_IMAGE_TAG?.split(":")[1] || "master",
            coin_app: appName,
            coin_app_version: appVersion,
            device: speculos_transport_1.reverseModelMap[model],
            device_os_version: firmware,
            run_id: runId,
            additional_args,
        },
    };
}
async function createSpeculosDeviceCI(deviceParams) {
    const runId = `${slugify(deviceParams.appName)}-${uniqueId()}`;
    try {
        const data = createStartPayload(deviceParams, runId);
        await githubApiRequest({ urlSuffix: START_WORKFLOW_ID, data });
        return {
            id: runId,
            port: speculosPort,
            appName: deviceParams.appName,
            appVersion: deviceParams.appVersion,
        };
    }
    catch (e) {
        console.warn(`Creating remote speculos ${deviceParams.appName}:${deviceParams.appVersion} failed with ${String(e)}`);
        return {
            id: runId,
            port: 0,
            appName: deviceParams.appName,
            appVersion: deviceParams.appVersion,
        };
    }
}
exports.createSpeculosDeviceCI = createSpeculosDeviceCI;
async function releaseSpeculosDeviceCI(runId) {
    const data = {
        ref: GITHUB_REF,
        inputs: {
            run_id: runId.toString(),
        },
    };
    await githubApiRequest({ urlSuffix: STOP_WORKFLOW_ID, data });
}
exports.releaseSpeculosDeviceCI = releaseSpeculosDeviceCI;
//# sourceMappingURL=speculosCI.js.map