import { basename } from 'node:path';

export const ZIP_SIGNATUTE = Buffer.from([0x50, 0x4b, 0x03, 0x04]);

export const getMetadata = () => ({
    name: 'curl',
    desc: 'Execute a request to the current profile API server URI',
    usage: 'afctl curl [-i] [-X METHOD] [-H HEADER, ...] PATH',
    options: {
        method: {
            keys: ['-X'],
            type: 'string',
            mandatory: false,
            multiple: false,
            helpLiteral: 'METHOD',
            helpText: 'HTTP method to use',
            index: 0
        },
        header: {
            keys: ['-H'],
            type: 'string',
            mandatory: false,
            multiple: true,
            helpLiteral: 'HEADER',
            helpText: 'Custom headers to include in the request',
            index: 1
        },
        data: {
            keys: ['-d'],
            type: 'string',
            mandatory: false,
            multiple: false,
            helpLiteral: 'DATA',
            helpText: 'Payload to include in the request body, or \'@\' followed by a file name to read the data from',
            index: 2
        },
        info: {
            keys: ['-i'],
            type: 'boolean',
            mandatory: false,
            multiple: false,
            helpLiteral: '',
            helpText: 'Print response status and headers',
            index: 3
        },
        path: {
            keys: [],
            type: 'string',
            mandatory: true,
            multiple: false,
            helpLiteral: 'PATH',
            helpText: 'Path and query string of the request URL',
            index: 3
        }
    }
});

export const run = async ({ args, config, console, fetch, fs }) => {
    if (!args.path) {
        const err = new Error('Failed to make a request. Mandatory positional argument PATH is missing.');
        throw err;
    }
    const profile = config?.profiles[config?.active_profile];
    if (!profile?.server_url) {
        const err = new Error('Failed to make a request. The API server URI is missing. Please login first.');
        throw err;
    }
    const method = (args.method ?? 'GET').toUpperCase();
    if (!['GET', 'POST', 'PUT', 'DELETE', 'PATCH'].includes(method)) {
        const err = new Error(
            `Failed to make a request. The '${method}' method is not supported. `+
            'Expected method to be one of GET | POST | PUT | DELETE | PATCH'
        );
        throw err;
    }
    const url = new URL(args.path, (new URL(profile.server_url)).origin);
    const options = {
        method,
        headers: {
            'Authorization': 'Bearer ' + profile.token.access_token
        }
    };
    if (args.header?.length) {
        console.debug('The following headers added to request:\n' + args.header.map(h => h + '\n'));
        for (const header of args.header) {
            const [key, val] = header.split(':', 2);
            options.headers[key.trim()] = val.trim();
        }
    }
    const headers = new Headers(options.headers);
    if (args.data) {
        if (!args.method) {
            options.method ??= 'POST';
        }
        let body;
        if (args.data[0] === '@') {
            console.debug(`Reading file: ${args.data.slice(1)}`);
            body = await fs.readFile(args.data.slice(1));
            // Check if ZIP file -> use multipart form
            // Otherwise, send raw file content
            if (ZIP_SIGNATUTE.equals(body.subarray(0, 4))) {
                console.debug('The ZIP file will be uploaded as multipart/form-data');
                const file = new File([body], basename(args.data));
                body = new FormData();
                body.append('file', file);
            }
        } else {
            // Don't encode JSON
            if (['{', '['].includes(args.data[0]) || !args.data.includes('=')) {
                body = args.data;
                console.debug(`Raw request payload: ${body}`);
            } else {
                if (!headers.has('content-type')) {
                    options.headers['Content-Type'] = 'application/x-www-form-urlencoded';
                }
                body = (new URLSearchParams(args.data)).toString();
                console.debug(`Form URL-encoded request payload: ${body}`);
            }
        }
        options.body = body;
    }
    const res = await fetch(url, options);
    if (args.info) {
        console.log(
            `${res.status} ${res.statusText}\n` +
            Array.from(res.headers).map(([key, val]) => `${key}: ${val}`).join('\n') +
            '\n'
        );
    }
    return [ await res.text() ];
};

