const { createHash } = require('node:crypto');
const { inspect } = require('node:util');

const { urlToRequest } = require('loader-utils');

const SIGNING_TOKEN = process.env.APIFY_SIGNING_TOKEN;
const signingUrl = new URL('https://api.apify.com/v2/tools/encode-and-sign');
signingUrl.searchParams.set('token', SIGNING_TOKEN || '');
const queue = [];
const cache = {};
let working = false;
let warnedAboutMissingToken = false;

function hash(source) {
    return createHash('sha1').update(source).digest('hex');
}

async function getHash(source) {
    const cacheKey = hash(source);

    if (cache[cacheKey]) {
        return cache[cacheKey];
    }

    const memory = source.match(/playwright|puppeteer/i) ? 4096 : 1024;
    const res = await (await fetch(signingUrl, {
        method: 'POST',
        body: JSON.stringify({
            input: JSON.stringify({ code: source }),
            options: {
                build: 'latest',
                contentType: 'application/json; charset=utf-8',
                memory,
                timeout: 180,
            },
        }),
        headers: {
            'Content-Type': 'application/json; charset=utf-8',
        },
    }));

    if (!res.ok) {
        console.error(`Signing failed: ${res.status} ${res.statusText}`, await res.text());
        return 'invalid-token';
    }

    const body = await res.json();

    if (!body.data || !body.data.encoded) {
        console.error(`Signing failed:' ${inspect(body.error) || 'Unknown error'}`, body);
        return 'invalid-token';
    }

    cache[cacheKey] = body.data.encoded;
    await new Promise((resolve) => setTimeout(resolve, 100));

    return body.data.encoded;
}

async function encodeAndSign(source) {
    if (!process.env.APIFY_SIGNING_TOKEN) {
        return 'invalid-token';
    }

    if (working) {
        return new Promise((resolve, reject) => {
            queue.push(() => {
                return getHash(source).then(resolve, reject);
            });
        });
    }

    let res;

    try {
        working = true;
        res = await getHash(source);

        while (queue.length) {
            await queue.shift()();
        }
    } finally {
        working = false;
    }

    return res;
}

module.exports = async function (code) {
    if (process.env.CRAWLEE_DOCS_FAST) {
        return { code, hash: 'fast' };
    }

    // Skip signing if token is not configured
    if (!SIGNING_TOKEN) {
        if (!warnedAboutMissingToken) {
            console.warn('APIFY_SIGNING_TOKEN is not set, skipping code signing for runnable examples.');
            warnedAboutMissingToken = true;
        }
        return { code, hash: 'unsigned' };
    }

    console.log(`Signing ${urlToRequest(this.resourcePath)}...`, { working, queue: queue.length });
    const codeHash = await encodeAndSign(code);
    return { code, hash: codeHash };
};
