import path from "node:path";

import { env } from "../config.js";
import { runTest } from "../misc/run-test.js";
import { loadJSON } from "../misc/load-from-fs.js";
import { Red, Bright } from "../misc/console-text.js";
import { randomizeCiphers } from "../misc/randomize-ciphers.js";

import { services } from "../processing/service-config.js";

const getTestPath = service => path.join('./src/util/tests/', `./${service}.json`);
const getTests = (service) => loadJSON(getTestPath(service));

// services that are known to frequently fail due to external
// factors (e.g. rate limiting)
const finnicky = new Set(['bilibili', 'instagram', 'facebook', 'youtube']);

const runTestsFor = async (service) => {
    const tests = getTests(service);
    let softFails = 0, fails = 0;

    if (!tests) {
        throw "no such service: " + service;
    }

    for (const test of tests) {
        const { name, url, params, expected } = test;
        const canFail = test.canFail || finnicky.has(service);

        try {
            await runTest(url, params, expected);
            console.log(`${service}/${name}: ok`);

        } catch(e) {
            softFails += !canFail;
            fails++;

            let failText = canFail ? `${Red('FAIL')} (ignored)` : Bright(Red('FAIL'));
            if (canFail && process.env.GITHUB_ACTION) {
                console.log(`::warning title=${service}/${name.replace(/,/g, ';')}::failed and was ignored`);
            }

            console.error(`${service}/${name}: ${failText}`);
            const errorString = e.toString().split('\n');
            let c = '┃';
            errorString.forEach((line, index) => {
                line = line.replace('!=', Red('!='));

                if (index === errorString.length - 1) {
                    c = '┗';
                }

                console.error(`   ${c}`, line);
            });
        }
    }

    return { fails, softFails };
}

const printHeader = (service, padLen) => {
    const padding = padLen - service.length;
    service = service.padEnd(1 + service.length + padding, ' ');
    console.log(service + '='.repeat(50));
}

const action = process.argv[2];
switch (action) {
    case "get-services":
        const fromConfig = Object.keys(services);

        const missingTests = fromConfig.filter(
            service => {
                const tests = getTests(service);
                return !tests || tests.length === 0
            }
        );

        if (missingTests.length) {
            console.error('services have no tests:', missingTests);
            process.exitCode = 1;
            break;
        }

        console.log(JSON.stringify(fromConfig));
        break;

    case "run-tests-for":
        const service = process.argv[3];

        env.streamLifespan = 10000;
        env.apiURL = 'http://x/';
        randomizeCiphers();

        try {
            const { softFails } = await runTestsFor(service);
            process.exitCode = Number(!!softFails);
        } catch(e) {
            console.error(e);
            process.exitCode = 1;
            break;
        }

        break;
    default:
        const maxHeaderLen = Object.keys(services).reduce((n, v) => v.length > n ? v.length : n, 0);
        const failCounters = {};

        env.streamLifespan = 10000;
        env.apiURL = 'http://x/';
        randomizeCiphers();

        for (const service in services) {
            printHeader(service, maxHeaderLen);
            const { fails, softFails } = await runTestsFor(service);
            failCounters[service] = fails;
            console.log();

            if (!process.exitCode && softFails)
                process.exitCode = 1;
        }

        console.log('='.repeat(50 + maxHeaderLen));
        console.log(
            Bright('total fails:'),
            Object.values(failCounters).reduce((a, b) => a + b)
        );
        for (const [ service, fails ] of Object.entries(failCounters)) {
            if (fails) console.log(`${Bright(service)} fails: ${fails}`);
        }
}