import { createProfile } from '../model/configuration.js';
import { parse as parseJWT, isExpired } from '../model/jwt.js';
import { knownEnv } from '../model/env.js';
import { entity, dimmed, success, failure, raw } from '../output/style.js';
import { table } from '../output/table.js';
import { toTimeString } from '../output/time.js';

const sortBy = (key) => (a, b) => {
    const valA = raw(a?.[key] ?? '');
    const valB = raw(b?.[key] ?? '');
    return valA < valB
        ? -1
        : valA > valB
            ? 1
            : 0;
};

export const getMetadata = () => ({
    name: 'config',
    desc: 'Display actual tool configuration or switch/delete configuration profile',
    usage: 'afctl config [PROFILE] [-d] [-e]',
    options: {
        profile: {
            keys: [],
            type: 'string',
            mandatory: false,
            multiple: false,
            helpLiteral: 'PROFILE',
            helpText: 'Name of the configuration profile',
            index: 0
        },
        'delete': {
            keys: ['--delete', '-d'],
            type: 'boolean',
            mandatory: false,
            multiple: false,
            helpText: 'Delete configuration profile'
        },
        'env': {
            keys: ['--env', '-e'],
            type: 'boolean',
            mandatory: false,
            multiple: false,
            helpText: 'Display supported environment variables and their values'
        }
    }
});

export const getCompletion = ({args, config}) => {
    if (args._.length === 3) {
        return Object.keys(config.profiles).map(
            profile => `${profile}:${config.profiles[profile].server_url}`
        );
    }
    return undefined;
};

export const run = async ({args, config, env, cli, console}) => {
    if (args.profile) {
        if (!config.profiles[args.profile]) {
            const err = new Error(
                `The profile '${entity(args.profile)}' is not in the list ` +
                `of available profiles (${Object.keys(config.profiles).map(n => entity(n)).join(', ')}).`
            );
            throw err;
        } else {
            console.debug(`Profile '${args.profile}' found in the list of available profiles`);
        }
        if (args.delete) {
            // delete profile
            console.debug(`Deleting profile '${args.profile}' from configuration`);
            delete config.profiles[args.profile];
            if (Object.keys(config.profiles).length === 0) {
                // re-create default profile, if all profiles were deleted
                console.debug('Creating \'default\' profile');
                config.profiles.default = createProfile();
            }
            if (args.profile === config.active_profile) {
                // switch to first existing profile, if active profile was deleted
                const active_profile = Object.keys(config.profiles)[0];
                console.debug(`Switching active profile to '${active_profile}'`);
                config.active_profile = active_profile;
            }
        } else {
            // switch to provided profile
            console.debug(`Switching active profile to '${args.profile}'`);
            config.active_profile = args.profile;
        }
        console.debug('Saving updated configuration');
        if(false === await config.save()) {
            const err = new Error(
                `Failed to save configuration to file '${dimmed(config.path)}'. ` +
                'Please, check that you have sufficient permissions to write into it ' +
                'and it is not open in other application.'
            );
            throw err;
        }
    }
    const profile = config.profiles[config.active_profile];
    if (!profile) {
        const err = new Error(
            `Configuration file '${dimmed(config.path)}' is corrupted. ` +
            `The profile '${entity(config.active_profile)}' is defined as active, ` +
            'but it is not in the list of available profiles. To fix the problem ' +
            `login with '${dimmed(cli.bin + ' login ' + config.active_profile)}' ` +
            `or switch to different profile with '${dimmed(cli.bin + ' config PROFILE')}', ` +
            `where PROFILE is one of: ${Object.keys(config.profiles).map(n => entity(n)).join(', ')}.`
        );
        throw err;
    }
    const title = 'SAP Application Frontend Command Line Tool configuration\n';
    const tableData = {
        type: 'columns',
        headers: [
            { name: 'cli_version', title: 'CLI version' },
            { name: 'configuration_file', title: 'Configuration file' },
            { name: 'active_profile', title: 'Active profile' },
            { name: 'available_profiles', title: 'Available profiles' },
            { name: 'installed_plugins', title: 'Installed plugins' },
        ],
        data: [{
            cli_version: cli.version,
            configuration_file: config.path,
            active_profile: config.active_profile,
            available_profiles: Object.keys(config.profiles).join(', '),
            installed_plugins: Object.keys(config.plugins ?? {}).join(', ')
        }]
    };

    // Environment variables configuration
    const prefix = cli.bin.toUpperCase() + '_';
    const knownEnvNames = new Set(Object.keys(knownEnv(cli)));
    const providedEnvNames = new Set(Object.keys(env).map(
        name => prefix + name
    ));
    const allEnvNames = Array.from(knownEnvNames.union(providedEnvNames));
    const tableEnv = {
        type: 'rows',
        headers: [
            { name: 'status', title: '' },
            { name: 'name', title: 'Name' },
            { name: 'value', title: 'Value' },
        ],
        data: allEnvNames.map(name => {
            const value = env[name.slice(prefix.length)];
            if (knownEnvNames.has(name)) {
                if (typeof value === 'undefined') {
                    return { status: dimmed('•'), name: dimmed(name), value: dimmed('-') };
                }
                return { status: success('✔'), name, value };
            }
            return { status: failure('?'), name: failure(name), value: failure(value) };
        }).sort(sortBy('name'))
    };
    const envItems = args.env ? [
        '',
        entity('Environment Variables'),
        '',
        table(tableEnv)
    ] : [];

    // No server URL, e.g. first time run
    if (!profile.server_url) {
        return [
            title,
            table(tableData),
            ...envItems,
            '\nYou are currently not logged-in to any SAP Application Frontend environment.\n' +
            `See '${dimmed(cli.bin + ' login --help')}' for more details on how to login.`
        ];
    }

    tableData.headers.push({ name: 'server_url', title: 'API server URI' });
    tableData.data[0].server_url = profile.server_url;
    // Server URL, but no token -> logged out
    if (!profile.token?.access_token) {
        return [
            title,
            table(tableData),
            ...envItems,
            '\nYou are currently logged-out.\n' +
            `To login back to the same profile run '${dimmed(cli.bin + ' login ' + config.active_profile)}'`
        ];
    }

    const jwt = parseJWT(profile.token.access_token);
    tableData.headers.push(
        { name: 'iss', title: 'Authorization server' },
        { name: 'apptid', title: 'Tenant ID' },
        { name: 'user', title: 'User' },
        { name: 'iat', title: 'Last login' },
        { name: 'exp', title: 'Login expiration' }
    );
    Object.assign(tableData.data[0], {
        iss: jwt.iss,
        apptid: jwt.app_tid ?? jwt.ext_attr?.subaccountid,
        user: jwt.email,
        iat: toTimeString(jwt.iat * 1000),
        exp: (isExpired(jwt) ? failure : success)(toTimeString(jwt.exp * 1000))
    });
    return [
        title,
        table(tableData),
        ...envItems
    ];
};
