Source: Commands/Giveaway/index.js

/* eslint-disable max-len */
/* Requires */
const fs = require('fs');

/* Importa módulos, ajuste o local conforme onde usar esse sistema */
const Indexer = require('../../index');

/* JSON's | Utilidades */
const envInfo = JSON.parse(fs.readFileSync(`${__dirname}/utils.json`));
const giveaways = {
    prize: 0,
};
const timeouts = {};

/**
 * Retorna todos os detalhes do ambiente (`envInfo`).
 *
 * @returns {Object} O objeto `envInfo`, que contém os detalhes do ambiente da execução.
 */
function ambientDetails() {
    /* Retorna a envData */
    return envInfo;
}

/* Define o vencedor do sorteio, função de uso exclusivo daqui, então sem envInfo neste */
function pickWinner(users) {
    /* Calcula o total de tickets somando a quantidade de tickets de todos os usuários */
    const totalTickets = Object.values(users).reduce((total, user) => total + user.tickets, 0);

    /* Gera um número aleatório entre 0 e o total de tickets */
    const winningNumber = Math.floor(Math.random() * totalTickets);

    /* Inicializa a contagem de tickets */
    let count = 0;
    let winner;

    /* Utilize o método forEach para iterar sobre as entradas do objeto */
    Object.entries(users).forEach(([user, userData]) => {
        /* Adiciona a quantidade de tickets do usuário atual à contagem */
        count += userData.tickets;

        /* Se a contagem ultrapassar o número sorteado e ainda não tiver um vencedor */
        if (count > winningNumber && !winner) {
            /* Define o vencedor como o usuário atual */
            winner = user;
        }
    });

    /* Se nenhum usuário for selecionado, retorna o primeiro */
    return winner || Object.keys(users)[0];
}

/* Define a chance de vencer, função de uso exclusivo daqui, então sem envInfo neste */
function winChances(users, player) {
    /* Calcula o total de tickets somando a quantidade de tickets de todos os usuários */
    const totalTickets = Object.values(users).reduce((total, user) => total + user.tickets, 0);

    /* Obtém a quantidade de tickets do jogador específico */
    const playerTickets = users[player].tickets;

    /* Calcula a chance de vencer do jogador específico */
    const winningChance = (playerTickets / totalTickets) * 100;

    /* Limita o resultado a duas casas decimais */
    return winningChance.toFixed(2);
}

/* Cria a função de comando */
async function lotteryStart(
    kill = envInfo.functions.exec.arguments.kill.value,
    env = envInfo.functions.exec.arguments.env.value,
) {
    /* Define um resultado padrão */
    envInfo.results.value = false;

    /* Define o sucesso */
    envInfo.results.success = false;

    /* Try-Catch para casos de erro */
    try {
        /* Se recebeu tudo corretamente, se der ruim, não fará nada */
        if (typeof kill === 'object' && typeof env === 'object') {
            /* Obtém os dados da raiz */
            const {
                chatId,
                user,
                isOwner,
                reply,
                arks,
                argl,
                isGroupMsg,
                leveling,
            } = env.value;

            /* Define o alias na envInfo */
            envInfo.alias = env.value.alias;

            /* Se não for grupo */
            if (!isGroupMsg) {
                /* Manda a mensagem só de grupos */
                envInfo.results.value = await kill.sendMessage(chatId, { text: Indexer('sql').languages(region, 'Extras', 'OnlyGroups', true, true, envInfo).value }, reply);

                /* Define o menu de ajuda */
            } else if (arks.includes('--help-dev') && isOwner === true) {
                /* Manda a mensagem de ajuda de dev */
                envInfo.results.value = await kill.sendMessage(chatId, { text: Indexer('sql').languages(region, 'Helper', 'Developer', true, true, envInfo).value }, reply);

                /* Menu de ajuda normal */
            } else if (arks.includes('--help')) {
                /* Envia sem detalhes de dev */
                envInfo.results.value = await kill.sendMessage(chatId, { text: Indexer('sql').languages(region, 'Helper', 'User', true, true, envInfo).value }, reply);

                /* Se for grupo */
            } else if (isGroupMsg) {
                /* Define o chat atual na giveaway */
                giveaways[chatId] = giveaways[chatId] || {};
                timeouts[chatId] = timeouts[chatId] || {
                    clock: '',
                    time: 0,
                    cooldown: 0,
                };

                /* Verifica se já fez a loteria do 'dia' */
                if (Date.now() >= timeouts[chatId].cooldown) {
                    /* Se for compra */
                    if (argl[0] === '-buy') {
                        /* Dados da loteria a fazer */
                        const insertUser = {
                            tickets: /[0-9]+/gi.test(argl[1]) ? Math.abs(Number(argl[1])) : 1,
                        };

                        /* Adiciona o valor dos tickets da pessoa */
                        insertUser.coins = insertUser.tickets * Number(envInfo.parameters.ticketValue.value);

                        /* Valor dos tickets atuais somados, usado para verificar se tem o valor a pagar */
                        let ticketsCoins = insertUser.coins;

                        /* Se existir na loteria já */
                        if (Object.keys(giveaways[chatId]).includes(user)) {
                            /* Adiciona o valor dos tickets já existentes */
                            ticketsCoins += giveaways[chatId][user].coins;
                        }

                        /* Verifica se tem o dinheiro */
                        if (leveling.coin >= ticketsCoins) {
                            /* Se já existir na loteria, adiciona os valores como tickets extras */
                            if (Object.keys(giveaways[chatId]).includes(user)) {
                                /* Adiciona o valor dos tickets ao existente */
                                giveaways[chatId][user].tickets += insertUser.tickets;

                                /* Agora adiciona o valor que vai cobrar */
                                giveaways[chatId][user].coins += insertUser.coins;

                                /* Se não, cria na database */
                            } else giveaways[chatId][user] = insertUser;

                            /* Adiciona os valores pagos no premio final */
                            giveaways.prize = Object.values(giveaways[chatId]).reduce((total, usert) => total + usert.coins, 0);
                            giveaways.prize = parseInt(giveaways.prize + Math.random() * (giveaways.prize * envInfo.parameters.lotteryExtra.value), 10);

                            /* Se já tiver dado o total minimo de participantes */
                            if (Object.keys(giveaways[chatId]).length >= envInfo.parameters.lotteryUsers.value) {
                                /* Define o tempo atual se for a primeira vez que atingiu o total de users */
                                if (timeouts[chatId].time === 0 && timeouts[chatId].clock === '') {
                                    /* Define a hora atual para calcular o tempo restante */
                                    timeouts[chatId].time = Date.now() + envInfo.parameters.lotteryTime.value;

                                    /* Define o timeout para começar */
                                    timeouts[chatId].clock = setTimeout(async () => {
                                        /* Define os participantes */
                                        const lotteryPlayers = giveaways[chatId];

                                        /* Remove valor a valor de quem fez a compra dos tickets */
                                        for (let i = 0; i < lotteryPlayers.length; i += 1) {
                                            /* Se não tiver o valor por ter perdido ele já, ficará devendo */
                                            Indexer('sql').update('leveling', lotteryPlayers[i], chatId, 'coin', -giveaways[chatId][lotteryPlayers[i]].coins);
                                        }

                                        /* Define o ganhador */
                                        const winnerUser = pickWinner(lotteryPlayers);

                                        /* Avisa o grupo do vencedor mostrando os ganhos dele */
                                        envInfo.results.value = await kill.sendMessage(chatId, {
                                            text: Indexer('sql').languages(region, 'Typings', 'Winner', true, true, {
                                                winner: winnerUser.replace(/@s.whatsapp.net/gi, ''),
                                                win: giveaways.prize,
                                                prize: 'Í-Coins',
                                            }).value,
                                            mentions: [winnerUser],
                                        }, reply);

                                        /* Deposita os ganhos */
                                        Indexer('sql').update('leveling', winnerUser, chatId, 'coin', giveaways.prize);

                                        /* Reseta os dados de loteria atual do grupo */
                                        giveaways[chatId] = {};
                                        timeouts[chatId] = {
                                            clock: '',
                                            time: 0,
                                            cooldown: Date.now() + envInfo.parameters.lotteryCooldown.value,
                                        };

                                        /* Determina o tempo, padrão de 5 minutos */
                                    }, envInfo.parameters.lotteryTime.value);
                                }

                                /* Diz que comprou os tickets e da o aviso de que deu o tempo limite e inicia a contar */
                                envInfo.results.value = await kill.sendMessage(chatId, {
                                    text: Indexer('sql').languages(region, 'Typings', 'Start', true, true, {
                                        chance: winChances(giveaways[chatId], user),
                                        amount: giveaways[chatId][user].tickets,
                                        item: 'Tickets',
                                        win: giveaways.prize,
                                        prize: 'Í-Coins',
                                        price: giveaways[chatId][user].coins,
                                        payment: 'Í-Coins',
                                        remain: Indexer('number').format(timeouts[chatId].time - Date.now()).overall,
                                    }).value,
                                }, reply);

                                /* Caso ainda não deu a quantidade de players */
                            } else {
                                /* Manda a mensagem normal de compra de ticket e diz que falta X players */
                                envInfo.results.value = await kill.sendMessage(chatId, {
                                    text: Indexer('sql').languages(region, 'Typings', 'Buy', true, true, {
                                        chance: winChances(giveaways[chatId], user),
                                        amount: giveaways[chatId][user].tickets,
                                        item: 'Tickets',
                                        win: giveaways.prize,
                                        prize: 'Í-Coins',
                                        price: giveaways[chatId][user].coins,
                                        payment: 'Í-Coins',
                                        waiting: envInfo.parameters.lotteryUsers.value - Object.keys(giveaways[chatId]).length,
                                    }).value,
                                }, reply);
                            }

                            /* Se não tem o dinheiro */
                        } else {
                            /* Envia a mensagem de não tem dinheiro */
                            envInfo.results.value = await kill.sendMessage(chatId, {
                                text: `${Indexer('sql').languages(region, 'Typings', 'Need', true, true, {
                                    need: ticketsCoins,
                                    item: 'Í-Coins',
                                    have: leveling.coin,
                                    amount: (ticketsCoins - leveling.coin),
                                }).value}\nMax Tickets: ${(leveling.coin / Number(envInfo.parameters.ticketValue.value)).toFixed(0)}`,
                            }, reply);
                        }

                        /* Se for para checar quanto tempo resta para o sorteio */
                    } else if (argl[0] === '-time' && timeouts[chatId].time !== 0) {
                        /* Envia a mensagem com o tempo restante */
                        envInfo.results.value = await kill.sendMessage(chatId, { text: Indexer('number').format(timeouts[chatId].time - Date.now()).overall }, reply);

                        /* Se for para checar os participantes */
                    } else if (argl[0] === '-users' && Object.keys(giveaways[chatId]).length > 0) {
                        /* Define o placar de jogadores */
                        const userList = Object.entries(giveaways[chatId]).map(([key, obj], index) => {
                            /* Obtém o nome do usuário */
                            const userData = Indexer('sql').get('personal', key, chatId).value;

                            /* Retorna a string formatada */
                            return `${index + 1}. ${userData.name.text} ~ ${userData.name.number.slice(0, 9)}...\n🎫 ${obj.tickets} Tickets\n💸 ${obj.coins}$ Í-Coins`;

                            /* Une ela com as outras se tiver 2 ou mais players */
                        }).join('\n\n');

                        /* Envia a mensagem com o tempo restante */
                        envInfo.results.value = await kill.sendMessage(chatId, { text: userList }, reply);

                        /* Se não tem ninguém participando */
                    } else if (Object.keys(giveaways[chatId]).length === 0) {
                        /* Envia a mensagem dizendo pra comprar um ticket */
                        envInfo.results.value = await kill.sendMessage(chatId, { text: Indexer('sql').languages(region, 'Typings', 'NoGames', true, true, envInfo).value }, reply);

                        /* Manda o menu de ajuda */
                    } else {
                        /* Envia sem detalhes de dev */
                        envInfo.results.value = await kill.sendMessage(chatId, { text: Indexer('sql').languages(region, 'Helper', 'User', true, true, envInfo).value }, reply);
                    }

                    /* Se já fez a loteria do dia */
                } else {
                    /* Envia a mensagem de loteria já executada com o tempo restante para voltar  */
                    envInfo.results.value = await kill.sendMessage(chatId, {
                        text: Indexer('sql').languages(region, 'Typings', 'Wait', true, true, {
                            resttime: Indexer('number').format(timeouts[chatId].cooldown - Date.now()).overall,
                        }).value,
                    }, reply).value;
                }
            }
        }

        /*
            Define o sucesso, se seu comando der erro isso jamais será chamado
            Então o success automaticamente será false em falhas
        */
        envInfo.results.success = true;

        /* Caso de algum erro */
    } catch (error) {
        /* Insere tudo na envInfo */
        logging.echoError(error, envInfo, __dirname);

        /* Avisa que deu erro, manda o erro e data ao sistema S.E.R (Send/Special Error Report) */
        await kill.sendMessage(env.value.chatId, {
            text: Indexer('sql').languages(region, 'S.E.R', error, true, true, {
                command: 'LOTTERY',
                time: (new Date()).toLocaleString(),
            }).value,
        }, env.value.reply);
    }

    /* Retorna os resultados */
    return logging.postResults(envInfo);
}

/**
 * Restaura o ambiente e atualiza as exportações do módulo com a funcionalidade principal
 * @param {Object} [changeKey={}] - Chaves personalizadas para atualizar o envInfo
 * @param {Object} [envFile=envInfo] - Objeto com informações do ambiente
 * @param {string} [dirname=__dirname] - Caminho do diretório atual
 * @returns {Object} Exportações do módulo com todas as funções configuradas
 */
/* eslint-disable-next-line no-return-assign */
const resetLocal = (
    changeKey = {},
    envFile = envInfo,
    dirname = __dirname,
) => module.exports = logging.resetAmbient({
    functions: {
        [envInfo.exports.env]: { value: ambientDetails },
        [envInfo.exports.messedup]: { value: logging.echoError },
        [envInfo.exports.poswork]: { value: logging.postResults },
        [envInfo.exports.reset]: { value: resetLocal },
        [envInfo.exports.exec]: { value: lotteryStart },
    },
    parameters: {
        location: { value: __filename },
    },
}, envFile, changeKey, dirname);
resetLocal();