const modulename = 'WebServer:AdminManagerActions';
import { customAlphabet } from 'nanoid';
import dict49 from 'nanoid-dictionary/nolookalikes';
import got from '@lib/got';
import consts from '@shared/consts';
import consoleFactory from '@lib/console';
import { AuthedCtx } from '@modules/WebServer/ctxTypes';
const console = consoleFactory(modulename);

//Helpers
const nanoid = customAlphabet(dict49, 20);
//NOTE: this desc misses that it should start and end with alphanum or _, and cannot have repeated -_.
const nameRegexDesc = 'up to 20 characters containing only letters, numbers and the characters \`_.-\`';
const cfxHttpReqOptions = {
    timeout: { request: 6000 },
};
type ProviderDataType = {id: string, identifier: string};

/**
 * Returns the output page containing the admins.
 */
export default async function AdminManagerActions(ctx: AuthedCtx) {
    //Sanity check
    if (typeof ctx.params?.action !== 'string') {
        return ctx.utils.error(400, 'Invalid Request');
    }
    const action = ctx.params.action;

    //Check permissions
    if (!ctx.admin.testPermission('manage.admins', modulename)) {
        return ctx.send({
            type: 'danger',
            message: 'You don\'t have permission to execute this action.',
        });
    }

    //Delegate to the specific action handler
    if (action == 'add') {
        return await handleAdd(ctx);
    } else if (action == 'edit') {
        return await handleEdit(ctx);
    } else if (action == 'delete') {
        return await handleDelete(ctx);
    } else {
        return ctx.send({
            type: 'danger',
            message: 'Unknown action.',
        });
    }
};


/**
 * Handle Add
 */
async function handleAdd(ctx: AuthedCtx) {
    //Sanity check
    if (
        typeof ctx.request.body.name !== 'string'
        || typeof ctx.request.body.citizenfxID !== 'string'
        || typeof ctx.request.body.discordID !== 'string'
        || ctx.request.body.permissions === undefined
    ) {
        return ctx.utils.error(400, 'Invalid Request - missing parameters');
    }

    //Prepare and filter variables
    const name = ctx.request.body.name.trim();
    const password = nanoid();
    const citizenfxID = ctx.request.body.citizenfxID.trim();
    const discordID = ctx.request.body.discordID.trim();
    let permissions = (Array.isArray(ctx.request.body.permissions)) ? ctx.request.body.permissions : [];
    permissions = permissions.filter((x: unknown) => typeof x === 'string');
    if (permissions.includes('all_permissions')) permissions = ['all_permissions'];


    //Validate name
    if (!consts.regexValidFivemUsername.test(name)) {
        return ctx.send({type: 'danger', markdown: true, message: `**Invalid username, it must follow the rule:**\n${nameRegexDesc}`});
    }

    //Validate & translate FiveM ID
    let citizenfxData: ProviderDataType | undefined;
    if (citizenfxID.length) {
        try {
            citizenfxData = {
                id: citizenfxID,
                identifier: citizenfxID,
            };
        } catch (error) {
            console.error(`Failed to resolve CitizenFX ID to game identifier with error: ${(error as Error).message}`);
        }
    }

    //Validate Discord ID
    let discordData: ProviderDataType | undefined;
    if (discordID.length) {
        if (!consts.validIdentifierParts.discord.test(discordID)) {
            return ctx.send({type: 'danger', message: 'Invalid Discord ID'});
        }
        discordData = {
            id: discordID,
            identifier: `discord:${discordID}`,
        };
    }

    //Check for privilege escalation
    if (!ctx.admin.isMaster && !ctx.admin.permissions.includes('all_permissions')) {
        const deniedPerms = permissions.filter((x: string) => !ctx.admin.permissions.includes(x));
        if (deniedPerms.length) {
            return ctx.send({
                type: 'danger',
                message: `You cannot give permissions you do not have:<br>${deniedPerms.join(', ')}`,
            });
        }
    }

    //Add admin and give output
    try {
        await txCore.adminStore.addAdmin(name, citizenfxData, discordData, password, permissions);
        ctx.admin.logAction(`Adding user '${name}'.`);
        return ctx.send({type: 'showPassword', password});
    } catch (error) {
        return ctx.send({type: 'danger', message: (error as Error).message});
    }
}


/**
 * Handle Edit
 */
async function handleEdit(ctx: AuthedCtx) {
    //Sanity check
    if (
        typeof ctx.request.body.name !== 'string'
        || typeof ctx.request.body.citizenfxID !== 'string'
        || typeof ctx.request.body.discordID !== 'string'
        || ctx.request.body.permissions === undefined
    ) {
        return ctx.utils.error(400, 'Invalid Request - missing parameters');
    }

    //Prepare and filter variables
    const name = ctx.request.body.name.trim();
    const citizenfxID = ctx.request.body.citizenfxID.trim();
    const discordID = ctx.request.body.discordID.trim();

    //Check if editing himself
    if (ctx.admin.name.toLowerCase() === name.toLowerCase()) {
        return ctx.send({type: 'danger', message: '(ERR0) You cannot edit yourself.'});
    }

    //Validate & translate permissions
    let permissions;
    if (Array.isArray(ctx.request.body.permissions)) {
        permissions = ctx.request.body.permissions.filter((x: unknown) => typeof x === 'string');
        if (permissions.includes('all_permissions')) permissions = ['all_permissions'];
    } else {
        permissions = [];
    }

    //Validate & translate FiveM ID
    let citizenfxData: ProviderDataType | undefined;
    if (citizenfxID.length) {
        try {
            citizenfxData = {
                id: citizenfxID,
                identifier: citizenfxID,
            };
        } catch (error) {
            console.error(`Failed to resolve CitizenFX ID to game identifier with error: ${(error as Error).message}`);
        }
    }

    //Validate Discord ID
    //FIXME: you cannot remove a discord id by erasing from the field
    let discordData: ProviderDataType | undefined;
    if (discordID.length) {
        if (!consts.validIdentifierParts.discord.test(discordID)) {
            return ctx.send({type: 'danger', message: 'Invalid Discord ID'});
        }
        discordData = {
            id: discordID,
            identifier: `discord:${discordID}`,
        };
    }

    //Check if admin exists
    const admin = txCore.adminStore.getAdminByName(name);
    if (!admin) return ctx.send({type: 'danger', message: 'Admin not found.'});

    //Check if editing an master admin
    if (!ctx.admin.isMaster && admin.master) {
        return ctx.send({type: 'danger', message: 'You cannot edit an admin master.'});
    }

    //Check for privilege escalation
    if (permissions && !ctx.admin.isMaster && !ctx.admin.permissions.includes('all_permissions')) {
        const deniedPerms = permissions.filter((x: string) => !ctx.admin.permissions.includes(x));
        if (deniedPerms.length) {
            return ctx.send({
                type: 'danger',
                message: `You cannot give permissions you do not have:<br>${deniedPerms.join(', ')}`,
            });
        }
    }

    //Add admin and give output
    try {
        await txCore.adminStore.editAdmin(name, null, citizenfxData, discordData, permissions);
        ctx.admin.logAction(`Editing user '${name}'.`);
        return ctx.send({type: 'success', refresh: true});
    } catch (error) {
        return ctx.send({type: 'danger', message: (error as Error).message});
    }
}


/**
 * Handle Delete
 */
async function handleDelete(ctx: AuthedCtx) {
    //Sanity check
    if (typeof ctx.request.body.name !== 'string') {
        return ctx.utils.error(400, 'Invalid Request - missing parameters');
    }
    const name = ctx.request.body.name.trim();

    //Check if deleting himself
    if (ctx.admin.name.toLowerCase() === name.toLowerCase()) {
        return ctx.send({type: 'danger', message: "You can't delete yourself."});
    }

    //Check if admin exists
    const admin = txCore.adminStore.getAdminByName(name);
    if (!admin) return ctx.send({type: 'danger', message: 'Admin not found.'});

    //Check if editing an master admin
    if (admin.master) {
        return ctx.send({type: 'danger', message: 'You cannot delete an admin master.'});
    }

    //Delete admin and give output
    try {
        await txCore.adminStore.deleteAdmin(name);
        ctx.admin.logAction(`Deleting user '${name}'.`);
        return ctx.send({type: 'success', refresh: true});
    } catch (error) {
        return ctx.send({type: 'danger', message: (error as Error).message});
    }
}
