import { mkdir, writeFile } from 'node:fs/promises';
import path from 'node:path';

import { assertRuntimeMetadataFinalized, loadConfig, loadTokens } from './config.js';
import {
    activateCapacityCoverage,
    cleanupCapacityFixture,
    materializeCalibrationConfig,
    prepareCapacitySecrets,
    seedCapacityFixture,
    verifyCapacityFixture,
} from './fixture.js';
import { measurePageNavigation } from './pageNavigation.js';
import { describeDryRun, runLoadTest } from './runner.js';
import { measureTurnFlush } from './turnFlush.js';

type Command =
    | 'run'
    | 'dry-run'
    | 'validate'
    | 'prepare'
    | 'seed'
    | 'verify-fixture'
    | 'activate-coverage'
    | 'measure-page-navigation'
    | 'measure-turn-cycle'
    | 'measure-turn-flush'
    | 'materialize-calibration'
    | 'cleanup';

const usage = (): never => {
    process.stderr.write(
        'usage: cli.ts <validate|dry-run|run|prepare|seed|verify-fixture|activate-coverage|measure-page-navigation|measure-turn-cycle|measure-turn-flush|materialize-calibration|cleanup> --config <file> [--tokens <0600-gitignored-file>] [--output <new-json-file>] [--confirm <load_schema>]\n'
    );
    process.exit(64);
};

const parseArguments = (
    argv: readonly string[]
): { command: Command; config: string; tokens?: string; output?: string; confirm?: string } => {
    const command = argv[0];
    if (
        ![
            'run',
            'dry-run',
            'validate',
            'prepare',
            'seed',
            'verify-fixture',
            'activate-coverage',
            'measure-page-navigation',
            'measure-turn-cycle',
            'measure-turn-flush',
            'materialize-calibration',
            'cleanup',
        ].includes(command ?? '')
    )
        usage();
    const values = new Map<string, string>();
    for (let index = 1; index < argv.length; index += 2) {
        const flag = argv[index];
        const value = argv[index + 1];
        if (!flag || !['--config', '--tokens', '--output', '--confirm'].includes(flag) || !value) usage();
        values.set(flag, value);
    }
    const config = values.get('--config');
    if (!config) usage();
    if (command === 'run' && (!values.get('--tokens') || !values.get('--output'))) usage();
    if (command === 'seed' && (!values.get('--tokens') || values.has('--output') || values.has('--confirm'))) usage();
    if (command === 'prepare' && (values.has('--tokens') || values.has('--output') || values.has('--confirm'))) usage();
    if (command === 'cleanup' && (!values.get('--confirm') || values.has('--tokens') || values.has('--output')))
        usage();
    if (command === 'verify-fixture' && (values.has('--tokens') || values.has('--output') || values.has('--confirm')))
        usage();
    if (
        command === 'activate-coverage' &&
        (!values.get('--confirm') || values.has('--tokens') || values.has('--output'))
    )
        usage();
    if (
        command === 'measure-page-navigation' &&
        (!values.get('--tokens') || !values.get('--output') || values.has('--confirm'))
    )
        usage();
    if (
        (command === 'measure-turn-flush' || command === 'measure-turn-cycle') &&
        (!values.get('--confirm') || !values.get('--output') || values.has('--tokens'))
    )
        usage();
    if (
        command === 'materialize-calibration' &&
        (!values.get('--output') || values.has('--tokens') || values.has('--confirm'))
    )
        usage();
    if (command === 'validate' && (values.has('--tokens') || values.has('--output') || values.has('--confirm')))
        usage();
    if (command === 'dry-run' && (values.has('--output') || values.has('--confirm'))) usage();
    if (command === 'run' && values.has('--confirm')) usage();
    return {
        command: command as Command,
        config: config!,
        ...(values.get('--tokens') ? { tokens: values.get('--tokens')! } : {}),
        ...(values.get('--output') ? { output: values.get('--output')! } : {}),
        ...(values.get('--confirm') ? { confirm: values.get('--confirm')! } : {}),
    };
};

const main = async (): Promise<void> => {
    const args = parseArguments(process.argv.slice(2));
    const workspaceRoot = path.resolve(import.meta.dirname, '../../..');
    const { config, sha256 } = await loadConfig(args.config);
    if (args.command === 'validate') {
        process.stdout.write(`${JSON.stringify({ valid: true, name: config.name, configSha256: sha256 })}\n`);
        return;
    }
    if (args.command === 'seed') {
        const result = await seedCapacityFixture({
            config,
            tokenPath: args.tokens!,
            workspaceRoot,
        });
        process.stdout.write(`${JSON.stringify(result)}\n`);
        return;
    }
    if (args.command === 'prepare') {
        process.stdout.write(`${JSON.stringify(await prepareCapacitySecrets({ config, workspaceRoot }))}\n`);
        return;
    }
    if (args.command === 'verify-fixture') {
        process.stdout.write(`${JSON.stringify(await verifyCapacityFixture(config))}\n`);
        return;
    }
    if (args.command === 'activate-coverage') {
        process.stdout.write(`${JSON.stringify(await activateCapacityCoverage(config, args.confirm!))}\n`);
        return;
    }
    if (args.command === 'measure-turn-flush' || args.command === 'measure-turn-cycle') {
        const output = path.resolve(args.output!);
        await mkdir(path.dirname(output), { recursive: true });
        const result = await measureTurnFlush({
            config,
            confirmation: args.confirm!,
            paced: args.command === 'measure-turn-cycle',
        });
        await writeFile(output, `${JSON.stringify(result, null, 2)}\n`, {
            encoding: 'utf8',
            flag: 'wx',
            mode: 0o600,
        });
        process.stdout.write(
            `${JSON.stringify({ completed: true, processedGenerals: result.throughput.processedGenerals, outputWritten: true })}\n`
        );
        return;
    }
    if (args.command === 'measure-page-navigation') {
        const tokens = await loadTokens(args.tokens!, workspaceRoot, config.capacity.authenticatedViewers);
        const output = path.resolve(args.output!);
        await mkdir(path.dirname(output), { recursive: true });
        const result = await measurePageNavigation({ config, tokens, workspaceRoot });
        await writeFile(output, `${JSON.stringify(result, null, 2)}\n`, {
            encoding: 'utf8',
            flag: 'wx',
            mode: 0o600,
        });
        process.stdout.write(
            `${JSON.stringify({ completed: true, viewers: result.fixture.viewers, outputWritten: true })}\n`
        );
        return;
    }
    if (args.command === 'materialize-calibration') {
        process.stdout.write(
            `${JSON.stringify(
                await materializeCalibrationConfig({
                    config,
                    outputPath: args.output!,
                    workspaceRoot,
                })
            )}\n`
        );
        return;
    }
    if (args.command === 'cleanup') {
        process.stdout.write(`${JSON.stringify(await cleanupCapacityFixture(config, args.confirm!))}\n`);
        return;
    }
    const tokens = args.tokens
        ? await loadTokens(args.tokens, workspaceRoot, config.capacity.authenticatedViewers)
        : null;
    if (args.command === 'dry-run') {
        process.stdout.write(
            `${JSON.stringify({ valid: true, tokenFileValidated: tokens !== null, configSha256: sha256, plan: describeDryRun(config) }, null, 2)}\n`
        );
        return;
    }
    assertRuntimeMetadataFinalized(config);
    const output = path.resolve(args.output!);
    await mkdir(path.dirname(output), { recursive: true });
    const result = await runLoadTest({ config, configSha256: sha256, tokens: tokens!, workspaceRoot });
    await writeFile(output, `${JSON.stringify(result, null, 2)}\n`, { encoding: 'utf8', flag: 'wx', mode: 0o600 });
    const failedRequests = result.phases.reduce(
        (total, phase) => total + Object.values(phase.metrics.http.errors).reduce((sum, count) => sum + count, 0),
        0
    );
    process.stdout.write(
        `${JSON.stringify({ completed: true, phases: result.phases.length, failedRequests, outputWritten: true })}\n`
    );
};

main().catch((error: unknown) => {
    let message = error instanceof Error ? error.message : 'unknown load-test error';
    for (const secret of [process.env.LOAD_TEST_DATABASE_URL, process.env.LOAD_TEST_REDIS_URL]) {
        if (secret) message = message.replaceAll(secret, '[redacted-url]');
    }
    message = message.replace(/(?:postgres(?:ql)?|rediss?):\/\/[^\s]+/giu, '[redacted-url]');
    process.stderr.write(`${message}\n`);
    process.exitCode = 1;
});
