package jesse.keeblarcraft.Commands;

import com.mojang.brigadier.arguments.IntegerArgumentType;
import com.mojang.brigadier.arguments.StringArgumentType;
import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Map;
import java.util.Map.Entry;

import static java.util.Map.entry;

import jesse.keeblarcraft.BankMgr.BankManager;
import jesse.keeblarcraft.BankMgr.IndividualAccount;
import jesse.keeblarcraft.BankMgr.IndividualBank;
import jesse.keeblarcraft.BankMgr.BankAccountType.ACCOUNT_TYPE;
import jesse.keeblarcraft.ChatStuff.ChatFormatting.COLOR_CODE;
import jesse.keeblarcraft.ChatStuff.ChatMsg;
import jesse.keeblarcraft.ConfigMgr.ConfigManager;
import net.minecraft.server.command.CommandManager;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.Text;
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;

public class BankCommands {
    private static List<String> FILLER_WORDS = new ArrayList<String>(List.of("of", "from", "as", "with", "name", "dubbed", "coined", "note"));
    private static String HELPCMD_HELP = "help";
    private static String HELPCMD_CREATE = "create";
    private static String HELPCMD_SELECT = "select";
    private static String HELPCMD_CLOSE = "close";
    private static String HELPCMD_REPORT = "report";
    private static String HELPCMD_BALANCE = "balance";
    private static String HELPCMD_EXAMPLES = "examples";
    private static String HELPCMD_MOVE = "move";
    private static String HELPCMD_SYNTAX = "syntax";
    private static String HELPCMD_ALIAS = "alias";
    private static String HELPCMD_WIRE = "wire";
    private static String HELPCMD_ACCOUNTS = "accounts";
    private static String HELPCMD_ADMIN_BALANCE_CHANGE = "admin-set-balance";
    private static String HELPCMD_ADMIN_BALANCE_GET = "admin-get-balance";
    private static String HELPCMD_ADMIN_CREATE_BANK = "admin-create-bank";
    private static String HELPCMD_ADMIN_CLOSE_BANK = "admin-close-bank";
    private static String HELPCMD_ADMIN_FORCE_WIRE = "admin-force-wire";
    private static String HELPCMD_ADMIN_LOCK_BANK = "admin-lock-bank";
    private static String HELPCMD_SET_SERVER_ALLOWANCE = "admin-set-server-allowance";
    private static String HELPCMD_GET_SERVER_ALLOWANCE = "admin-get-server-allowance";
    private static String HELPCMD_ADMIN_COMMANDS_LIST = "admin-commands-list";
    private static String HELPCMD_ADMIN_ACCOUNTS = "admin-accounts";
    private static String HELPCMD_ADMIN_ACCOUNTS_LIST = "admin-accounts-list";
    private static String HELPCMD_ADMIN_ACCOUNTS_MOVE = "admin-accounts-move";
    private static String HELPCMD_ADMIN_ACCOUNTS_FORCE_CLOSE = "admin-accounts-force-close";
    private static String HELPCMD_ADMIN_ACCOUNTS_ADD = "admin-accounts-add";
    private static String HELPCMD_ADMIN_ACCOUNTS_TRANSACTIONS = "admin-accounts-transactions";
    private static String HELPCMD_ADMIN_ACCOUNTS_LOCK = "admin-accounts-lock";

    ChatMsg msgFormatter = new ChatMsg();

    private static Map<String, String> HELP_COMMANDS = Map.ofEntries(
        entry
        (
            HELPCMD_HELP, "Usage: /bank help. This is the general help message for the bank system. For detailed help messages, please run /bank help <command OR subcommand>. List of possible " +
        "commands are: CREATE, SELECT, CLOSE, REPORT, BALANCE, EXAMPLES, WIRE, MOVE, SYNTAX, ALIAS"
        ),
        entry
        (
            HELPCMD_CREATE, "Usage: /bank create {checking|savings}. This will create a bank account with the default selected bank (/bank select {BANK_ID} to choose other)."
        ),
        entry
        (
            HELPCMD_SELECT, "Usage: /bank select {BANK_ID}. Do `/bank list` for a list of banks on the server."
        ),
        entry
        (
            HELPCMD_CLOSE, "Usage: /bank close {ACCOUNT_ID}. Do `/bank accounts list` to see all the accounts you have! Note: CANNOT close accounts that aren't 0 balance."
        ),
        entry
        (
            HELPCMD_REPORT, "Usage: /bank report [ACCOUNT_ID|none]. Optional args mean you can get a specific report list for one account; but if none specified we default to all accounts."
        ),
        entry
        (
            HELPCMD_BALANCE, "Usage: /bank balance [ACCOUNT_ID]. Optional args get balances for other accounts that aren't the default selected account. You may run `/bank accounts list` to " +
            "see all possible options to get a balance for."
        ),
        entry
        (
            HELPCMD_EXAMPLES, "Usage: /bank examples. This will print a much larger command with more thorough examples (and even shortcuts!) of all the commands from the bank system."
        ),
        entry
        (
            HELPCMD_MOVE, "Usage: /bank move {ACCOUNT_ID} to {BANK_ID}. The intended use of this command is to move accounts across banks, but you could also plug in another ACCOUNT_ID in " +
            " the `BANK_ID` part to perform an internal wire instead. NOTE: Moving might not succeed based on bank rules!"
        ),
        entry
        (
            HELPCMD_SYNTAX, "Usage: /bank syntax. This banking system was written to be human readable in command-form and often operates like a database. You can put certain filler-words inside your commands that are " +
        "parsed out at runtime if it helps with reading. Example command: \"/bank select 942-3449-42\" will work just fine to select a bank account as a primary account. HOWEVER, you can also " +
        "type \"/bank select for-bills as default\" so long as you aliased the account identifier as 'for-bills'. Much nicer to read and work with. [Related: /bank help aliases]"
        ),
        entry
        (
            HELPCMD_ALIAS, "Usage: /bank alias {ACCOUNT_ID} {UNIQUE_NAME}. Alias provides a handy way to refer to bank accounts by their unique names instead of long-form account ids. NOTE: Aliases " +
            "are ONLY unique within the context of an individual bank and not across all banks, but generally you can replace `ACCOUNT_ID` as seen in other commands with the alias if it exists."
        ),
        entry
        (
            HELPCMD_WIRE, "Usage: /bank wire {AMOUNT} to {ACCOUNT_ID|NAME}. It is recommended to wire a specific account ID over a name since it works if the other player is offline. If name is specified, " +
            " the wire will go through to that players default account. Use with caution!"
        ),
        entry
        (
            HELPCMD_ACCOUNTS, "Usage: /bank accounts {BANK_ID}. Prints out all of your banking information at a bank assuming you have any."
        ),
        entry
        (
            HELPCMD_ADMIN_BALANCE_CHANGE, "Usage: /bank admin {set-bal|add-money|subtract-money} {ACCOUNT_ID|BANK_ID} {AMOUNT}. This command covers set/add/subtract money. Usage varies on context, but general help command is going to be " +
            "/bank admin {set-bal|add-money|sub-money} {USER_ACCOUNT_ID|BANK_ROUTING_NUM} {amount}. Please note that the \"|\" in this context means OR - you do not put both! " +
            "So you can set a banks balance or accounts balance with this command."
        ),
        entry
        (
            HELPCMD_ADMIN_BALANCE_GET, "Usage: /bank admin get {USER_ACCOUNT_ID|BANK_ID}. Please note that the \"|\" in this means OR. Do NOT put both!"
        ),
        entry
        (
            HELPCMD_ADMIN_CREATE_BANK, "Usage: /bank admin create {new bank name}. Bank name must be longer than 4 letters!"
        ),
        entry
        (
            HELPCMD_ADMIN_CLOSE_BANK, "Usage: /bank admin close {BANK_ID}. Please be aware if the bank has an active balance or user accounts you will be asked to " +
            "confirm if this is really what you want to do. Money is not magic, so if you specify to close the bank the bank balance is transferred back " +
            "to the servers primary bank account & all user accounts money are transferred to the server bank. Accounts will automatically be opened to preserve " +
            "the money for users to claim because banks are not ordinarily supposed to just close. Only run this command if you absolutely have to!"
        ),
        entry
        (
            HELPCMD_ADMIN_FORCE_WIRE, "Usage: /bank admin wire {AMOUNT} from {ACCOUNT_ID} to {ACCOUNT_ID}. Please be aware this is a heavy handed command and may " +
            "unintentionally override a bank accounts default setting. A bank account with a rule that can't go negative WILL go negative if this command is " +
            "run because it's an administrative command! Use with caution"
        ),
        entry
        (
            HELPCMD_ADMIN_LOCK_BANK, "Usage: /bank admin lock-bank {BANK_ID}. Completely freezes all activity in and out of a bank."
        ),
        entry
        (
            HELPCMD_SET_SERVER_ALLOWANCE, "Usage: /bank admin set-server-allowance {amount} {interval}. Example usage: /bank admin set-server-allowance 1,000,000 " +
            "30d. Supported interval characters: d/m/y (day/month/year). These times are in MINECRAFT in-game time, not real life time! Months are assumed to be equally " +
            "30 days and years are 365 days. If you need precision, just specify your amount in days!"
        ),
        entry
        (
            HELPCMD_GET_SERVER_ALLOWANCE, "Usage: /bank admin get-server-allowance. Returns the amount of money in the allowance for server and the interval it is in. "
        ),
        entry
        (
            HELPCMD_ADMIN_COMMANDS_LIST, "Usage: /bank admin {sub-command} [optionals]. POSSIBLE ADMIN COMMANDS LIST: [set-bal, add-money, get-bal, sub-money, accounts, create-bank, close-bank, lock-bank, unlock, " +
            "force-wire, get-server-allowance, set-server-allowance]. Commands FROM [accounts] cmd => [list, move, force-close, add, transactions, lock]"
        ),
        entry
        (
            HELPCMD_ADMIN_ACCOUNTS, "Usage: /bank admin accounts {sub-command} [optionals]. This command has sub-commands that are expected. Possible => [list, move, force-close, add, transactions, lock]. General syntax is " +
            "/bank admin accounts [sub-command]"
        ),
        entry
        (
            HELPCMD_ADMIN_ACCOUNTS_ADD, "Usage: /bank admin accounts add {ONLINE_PLAYER} [BANK_ID]. Adds a regular checking account to a bank for a player who is online. " +
            "If BANK_ID not specified; defaults to the servers default bank"
        ),
        entry
        (
            HELPCMD_ADMIN_ACCOUNTS_FORCE_CLOSE, "Usage: /bank admin accounts force-close {ACCOUNT_ID}. Command not supported in this version."
        ),
        entry
        (
            HELPCMD_ADMIN_ACCOUNTS_LIST, "Usage: /bank admin accounts list {USERNAME}. Command not supported in this version."
        ),
        entry
        (
            HELPCMD_ADMIN_ACCOUNTS_LOCK, "Usage: /bank admin accounts lock {ACCOUNT_ID}. Command not supported in this version."
        ),
        entry
        (
            HELPCMD_ADMIN_ACCOUNTS_MOVE, "Usage: /bank admin accounts move {ACCOUNT_ID} to {BANK_ID}. Command not supported in this version."
        ),
        entry
        (
            HELPCMD_ADMIN_ACCOUNTS_TRANSACTIONS, "Usage: /bank admin accounts transactions {ACCOUNT_ID}. Command not supported in this version."
        )
    );

    ConfigManager config = new ConfigManager();

    /////////////////////////////////////////////////////////////////////////////
    /// @fn         RegisterCommands
    ///
    /// @brief      Registers all bank commands on the server
    /////////////////////////////////////////////////////////////////////////////
    public void RegisterCommands() {
        // Bank help
        CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> {
            var bankRoot = CommandManager.literal("bank").build();
            var help = CommandManager.literal("help").build();
            var helpTarget = CommandManager.argument("target", StringArgumentType.greedyString())
                .executes(context -> HelpCommand(context.getSource().getPlayer(), StringArgumentType.getString(context, "target")))
                .build();

            var info = CommandManager.literal("info")
                .executes(context -> ListBankInfo(context.getSource().getPlayer()))
                .build();

            bankRoot.addChild(info);

            var create = CommandManager.literal("create").build();
            var createAccount = CommandManager.literal("account").build();

            var checking = CommandManager.literal("checking")
                .executes(context -> CreateAccount(context.getSource().getPlayer(), ACCOUNT_TYPE.CHECKING))
                .build();

            var savings = CommandManager.literal("savings")
                .executes(context -> CreateAccount(context.getSource().getPlayer(), ACCOUNT_TYPE.SAVINGS))
                .build();

            bankRoot.addChild(create);
            create.addChild(createAccount);
            createAccount.addChild(checking);
            createAccount.addChild(savings);

            var wireCmd = CommandManager.literal("wire").build();
            var accountNum = CommandManager.argument("account", StringArgumentType.string()).build();
            var wireAmount = CommandManager.argument("amount", IntegerArgumentType.integer()).build();
            var wireReason = CommandManager.argument("reason", StringArgumentType.greedyString())
                .executes(context -> WireCommand(
                                                context.getSource().getPlayer(),
                                                IntegerArgumentType.getInteger(context, "amount"),
                                                StringArgumentType.getString(context, "account"),
                                                StringArgumentType.getString(context, "reason")
                                                ))
                .build();

            bankRoot.addChild(wireCmd);
            wireCmd.addChild(accountNum);
            accountNum.addChild(wireAmount);
            wireAmount.addChild(wireReason);

            var list = CommandManager.literal("list")
            .executes(context -> ListAllBanks(context.getSource().getPlayer()))
            .build();

            bankRoot.addChild(list);

            var selectBankAccount = CommandManager.literal("select-default-account").build();
            var selectBankAccountName = CommandManager.argument("global-account-id", StringArgumentType.string())
                .executes(context -> SelectDefaultAccount(context.getSource().getPlayer(), StringArgumentType.getString(context, "global-account-id")))
                .build();

            bankRoot.addChild(selectBankAccount);
            selectBankAccount.addChild(selectBankAccountName);

            var selectBank = CommandManager.literal("select-bank").build();
            var selectBankName = CommandManager.argument("global_bank_name", StringArgumentType.string())
                .executes(context -> SelectBank(context.getSource().getPlayer(), StringArgumentType.getString(context, "global_bank_name")))
                .build();

            bankRoot.addChild(selectBank);
            selectBank.addChild(selectBankName);



            var close = CommandManager.literal("close").build();
            var closeAccountId = CommandManager.argument("account", StringArgumentType.string())
                .executes(context -> CloseCommand(context.getSource().getPlayer(), StringArgumentType.getString(context, "account")))
                .build();

            bankRoot.addChild(close);
            close.addChild(closeAccountId);


            var moveCmd = CommandManager.literal("move").build();
            var moveAmount = CommandManager.argument("move_amount", IntegerArgumentType.integer()).build();
            var fromAccount = CommandManager.argument("from_account", StringArgumentType.string()).build();
            var toAccount = CommandManager.argument("to_account", StringArgumentType.string())
                .executes(context -> MoveCommand(
                                                context.getSource().getPlayer(),
                                                IntegerArgumentType.getInteger(context, "move_amount"),
                                                StringArgumentType.getString(context, "from_account"),
                                                StringArgumentType.getString(context, "to_account")
                                                ))
                .build();

            bankRoot.addChild(moveCmd);
            moveCmd.addChild(moveAmount);
            moveAmount.addChild(fromAccount);
            fromAccount.addChild(toAccount);


            var admin = CommandManager.literal("admin").build();
            // var accounts = CommandManager.literal("accounts").build();
            var subtractMoney = CommandManager.literal("sub-money").build();
            var subtractAmount = CommandManager.argument("sub_amount", IntegerArgumentType.integer()).build();
            var subtractAccount = CommandManager.argument("sub_account", StringArgumentType.string()).build();
            var subtractReason = CommandManager.argument("sub_reason", StringArgumentType.greedyString())
                .executes(context -> AdminSubtractMoney(
                                                        context.getSource().getPlayer(),
                                                        IntegerArgumentType.getInteger(context, "sub_amount"),
                                                        StringArgumentType.getString(context, "sub_account"),
                                                        StringArgumentType.getString(context, "sub_reason")
                                                        ))
                                                        .build();



            var addMoney = CommandManager.literal("add-money").build();
            var addAmount = CommandManager.argument("add_amount", IntegerArgumentType.integer()).build();
            var addAccount = CommandManager.argument("add_account", StringArgumentType.string()).build();
            var addReason = CommandManager.argument("add_reason", StringArgumentType.greedyString())
                .executes(context -> AdminAddMoney(
                                                    context.getSource().getPlayer(),
                                                    IntegerArgumentType.getInteger(context, "add_amount"),
                                                    StringArgumentType.getString(context, "add_account"),
                                                    StringArgumentType.getString(context, "add_reason")
                                                    ))
                .build();

            var setMoney = CommandManager.literal("set-bal").build();
            var setAmnt = CommandManager.argument("set_amount", IntegerArgumentType.integer()).build();
            var setAccount = CommandManager.argument("set_account", StringArgumentType.string()).build();
            var setReason = CommandManager.argument("set_reason", StringArgumentType.greedyString())
                .executes(context -> AdminSetMoney(
                                                    context.getSource().getPlayer(),
                                                    IntegerArgumentType.getInteger(context, "set_amount"),
                                                    StringArgumentType.getString(context, "set_account"),
                                                    StringArgumentType.getString(context, "set_reason")
                                                    ))
                .build();

            var getBal = CommandManager.literal("get-bal").build();
            var getBalTgt = CommandManager.argument("get_bal_target", StringArgumentType.string()).build();

            var createBank = CommandManager.literal("create-bank").build();
            var createBankName = CommandManager.argument("create_bank_name", StringArgumentType.string()).build();

            var closeBank = CommandManager.literal("close-bank").build();
            var closeBankName = CommandManager.argument("close_bank_name", StringArgumentType.string()).build();

            var setServerBal = CommandManager.literal("set-server-bal").build();
            var setServerBalAmnt = CommandManager.argument("set_server_bal_amount", StringArgumentType.string()).build();

            var setServerAllowance = CommandManager.literal("set-server-allowance").build();
            var setServerAllowanceAmount = CommandManager.argument("set_server_allowance", IntegerArgumentType.integer()).build();


            bankRoot.addChild(admin);

            admin.addChild(subtractMoney);
            subtractMoney.addChild(subtractAmount);
            subtractAmount.addChild(subtractAccount);
            subtractAccount.addChild(subtractReason);

            admin.addChild(addMoney);
            addMoney.addChild(addAmount);
            addAmount.addChild(addAccount);
            addAccount.addChild(addReason);

            admin.addChild(setMoney);
            setMoney.addChild(setAmnt);
            setAmnt.addChild(setAccount);
            setAccount.addChild(setReason);

            admin.addChild(getBal);
            getBal.addChild(getBalTgt);

            admin.addChild(createBank);
            createBank.addChild(createBankName);

            admin.addChild(closeBank);
            admin.addChild(closeBankName);

            admin.addChild(setServerBal);
            setServerBal.addChild(setServerBalAmnt);

            admin.addChild(setServerAllowance);
            setServerAllowance.addChild(setServerAllowanceAmount);


            // Building the argument tree here
            dispatcher.getRoot().addChild(bankRoot);
            bankRoot.addChild(help);
            help.addChild(helpTarget);
        });
    }

    public int SelectBank(ServerPlayerEntity player, String bank) {
        System.out.println("Select bank called");
        BankManager.GetInstance().ChangeDefaultSelectedBank(player, bank);
        return 0;
    }

    /////////////////////////////////////////////////////////////////////////////
    /// @fn         IsOperator
    ///
    /// @param[in]  player is the player to be tested
    ///
    /// @brief      Check to see if player is operator (move to util in future)
    ///
    /// @return     True if player is operator, false if not
    /////////////////////////////////////////////////////////////////////////////
    public Boolean IsOperator(ServerPlayerEntity player) {
        if (player.hasPermissionLevel(4)) {
            return true;
        } else {
            return false;
        }
    }

    public int AdminSubtractMoney(ServerPlayerEntity player, Integer amount, String account, String reason) {
        AdminBalanceChange(player, account, amount, "subtract", reason);
        return 0;
    }

    public int AdminAddMoney(ServerPlayerEntity player, Integer amount, String account, String reason) {
        AdminBalanceChange(player, account, amount, "add", reason);
        return 0;
    }

    public int AdminSetMoney(ServerPlayerEntity player, Integer amount, String account, String reason) {
        AdminBalanceChange(player, account, amount, "set", reason);
        return 0;
    }

    public String GetServerAllowance() {
        return "";
    }

    /////////////////////////////////////////////////////////////////////////////
    /// @fn         AdminBalanceChange
    ///
    /// @param[in]  player is player who ran command
    ///
    /// @param[in]  accountId is account id to change balance of
    ///
    /// @param[in]  amount is amount to change account balance
    ///
    /// @param[in]  type is type of fund change (parsed on @ref BankManager.java)
    ///
    /// @param[in]  optionalReason is the optional reason of balance change
    ///
    /// @brief      Admin command to change the balance of an account by some
    ///             amount
    /////////////////////////////////////////////////////////////////////////////
    public void AdminBalanceChange(ServerPlayerEntity player, String accountId, Integer amount, String type, String optionalReason) {
        BankManager.GetInstance().AdminChangeFunds(player, accountId, amount, type, optionalReason);
    }

    public void AdminGetBalance(String accountId) {

    }

    public void AdminWireMoney(String fromAccount, String toAccount, Integer amount, String optionalReason) {

    }

    public void AdminCreateBank(String bankName, Integer initialBankBalance, Integer kbicInsuredAmount) {

    }

    public void AdminCloseBank(String bankIdentifier, String optionalReason, Boolean forceClosure) {

    }

    public void AdminAccounts() {

    }

    public void LockBank(String bankIdentifier, String optionalReason) {

    }

    public void LockPlayer(String playerName, String optionalReason) {

    }

    public void LockBankAccount(String accountId, String optionalReason) {

    }

    // Possible code paths:
    // REQUIRED = {Routing # or Bank name}
    // OPTIONAL = []
    /////////////////////////////////////////////////////////////////////////////
    /// @fn         ManageAccounts
    ///
    /// @brief      Change the balance of a designated account
    /////////////////////////////////////////////////////////////////////////////
    public int ListBankInfo(ServerPlayerEntity sourcePlayer) {
        String bankName = BankManager.GetInstance().GetPlayerSelectedBank(sourcePlayer.getUuidAsString());
        IndividualBank bank = BankManager.GetInstance().GetBankByName(bankName);

        if (bank != null) {
            System.out.println("Grabbing user account information");
            List<IndividualAccount> userAccounts = bank.GetAccountsOfUser(sourcePlayer.getUuidAsString());
            sourcePlayer.sendMessage(Text.of("[BANK INFO FOR " + bankName.toUpperCase() + "]"));
            System.out.println("userAccounts size: " + userAccounts.size());

            for (int i = 0; i < userAccounts.size(); i++) {
                String accountNumber = userAccounts.get(i).GetAccountNumber();
                String globalAccountNumber = userAccounts.get(i).GetGlobalAccountNumber();
                List<String> accountHolders = userAccounts.get(i).GetAccountHolders();
                Integer accountBalance = userAccounts.get(i).GetAccountBalance();

                String l1 = "ACCOUNT NUMBER: " + msgFormatter.ColorMsg(accountNumber, COLOR_CODE.BLUE);
                String l2 = "GLOBAL ACCOUNT NUMBER: " + msgFormatter.ColorMsg(globalAccountNumber, COLOR_CODE.BLUE);
                String l3 = "HOLDERS: " + msgFormatter.ColorMsg(accountHolders, COLOR_CODE.GRAY);
                String l4 = "BALANCE: " + msgFormatter.ColorMsg(accountBalance, COLOR_CODE.GREEN);

                sourcePlayer.sendMessage((Text) msgFormatter.MakeCopyableTxt(l1, "Click to copy", accountNumber));
                sourcePlayer.sendMessage((Text) msgFormatter.MakeCopyableTxt(l2, "Click to copy", globalAccountNumber));
                sourcePlayer.sendMessage((Text) msgFormatter.MakeCopyableTxt(l3, "Click to copy", accountHolders));
                sourcePlayer.sendMessage((Text) msgFormatter.MakeCopyableTxt(l4, "Click to copy", accountBalance));
                sourcePlayer.sendMessage(Text.of("\n"));
            }
        } else {
            sourcePlayer.sendMessage(Text.of(msgFormatter.ColorMsg("That bank does not exist", COLOR_CODE.RED)));
        }
        return 0;
    }

    // TODO: make prettier
    public int ListAllBanks(ServerPlayerEntity sourcePlayer) {
        sourcePlayer.sendMessage(Text.of("Here is a list of available banks on the server: " + BankManager.GetInstance().GetAllBankNames()));
        return 0;
    }

    // Possible code paths:
    // REQUIRED (path 1) = {AMOUNT} [FROM:{INTERNAL ACCOUNT #|ALIAS}] TO:{INTERNAL ACCOUNT #|ALIAS} ***Note: can be assumed to be SELECTED default if not specified
    // REQUIRED (path 2) = {INTERNAL ACCOUNT #|ALIAS} {EXTERNAL BANK/FACTION ID}
    // OPTIONAL = []
    /////////////////////////////////////////////////////////////////////////////
    /// @fn         MoveCommand
    ///
    /// @param[in]  player is player who ran command
    ///
    /// @param[in]  argList is the argument list to be parsed
    ///
    /// @brief      Command to move account. Read above comment for code paths
    /////////////////////////////////////////////////////////////////////////////
    public int MoveCommand(ServerPlayerEntity sourcePlayer, Integer amount, String fromAccount, String toAccount) {
        return 0;
    }

    // Posible code paths:
    // REQUIRED = {ACCOUNT-ID|ALIAS} {ALIAS}
    // OPTIONAL = []
    /////////////////////////////////////////////////////////////////////////////
    /// @fn         AliasCommand
    ///
    /// @param[in]  player is player who ran command
    ///
    /// @param[in]  argList is the argument list to be parsed
    ///
    /// @brief      Aliases an account with a nick name
    /////////////////////////////////////////////////////////////////////////////
    public void AliasCommand(ServerPlayerEntity sourcePlayer, List<String> argList) {

        if (argList.size() > 0) {
            String newAlias = argList.get(0);
            /*
             * CALL TO BANKMGR TO SEE IF ALIAS ARG ALREADY EXISTS
            */
        } else {
            sourcePlayer.sendMessage(Text.of("Unrecognized alias command. Please run \"/bank help ALIAS\" for more information."));
        }
    }

    // Possible code paths:
    // REQUIRED = {...} valid argList required
    // OPTIONAL = []
    // NOTE: This is just a shorthand to get to `/bank create report`
    /////////////////////////////////////////////////////////////////////////////
    /// @fn         ReportCommand
    ///
    /// @param[in]  player is player who ran command
    ///
    /// @param[in]  argList is the argument list to be parsed
    ///
    /// @brief      Shorthand function call-through to CreateCommand for generating
    ///             an account(s) report.
    /////////////////////////////////////////////////////////////////////////////
    // public void ReportCommand(ServerPlayerEntity sourcePlayer, List<String> argList) {
    //     if (argList.size() > 0) {
    //         argList.add(0, "report"); // Since we lose 'report' when we pass it as an alias/passthrough; we just shim it into the first spot
    //         CreateCommand(sourcePlayer, argList);
    //     } else {
    //         sourcePlayer.sendMessage(Text.of("Unrecognized report command. Please run \"/bank help REPORT\" for more information."));
    //     }
    // }

    // Possible code paths:
    // REQUIRED = {}
    // OPTIONAL = [identifier|alias]
    public int BalanceCommand(ServerPlayerEntity sourcePlayer, List<String> argList) {
        String accountToBalance = "default";
        /*
         *
         * Code here to get default account from player
         *
        */

        if (argList.size() == 1) {
            /*
             * Set accountToBalance here
            */
        }

        /*
         * Return to player balance here in new chatutil function not yet made
        */

        return 0;
    }

    // Possible code paths:
    // REQUIRED = {ACCOUNT ID|ALIAS}
    // OPTIONAL = []
    public int CloseCommand(ServerPlayerEntity sourcePlayer, String account) {
        // if (argList.size() > 0) {
        //     String accountToClose = argList.get(0);
        //     /*
        //      * CALL TO BANKMGR TO DO STUFF
        //      */
        // } else {
        //     sourcePlayer.sendMessage(Text.of("Unrecognized close command. Please see \"/bank help CLOSE\" for more information."));
        // }
        return 0;
    }

    // Possible code paths:
    // required = {account id | alias}
    // optional = [default|secondary|backup]
    public int SelectDefaultAccount(ServerPlayerEntity sourcePlayer, String account) {
        BankManager.GetInstance().ChangeDefaultPlayerAccount(sourcePlayer, account);
        return 0;
    }

    /////////////////////////////////////////////////////////////////////////////
    /// @fn         WireCommand
    ///
    /// @param[in]  player is player who ran command
    ///
    /// @param[in]  argList is the argument list to be parsed
    ///
    /// @brief      Create wire on bank account
    /////////////////////////////////////////////////////////////////////////////
    public int WireCommand(ServerPlayerEntity sourcePlayer, Integer amount, String destAccount, String reason) {
        BankManager.GetInstance().InitiateBankFundsTransfer(sourcePlayer, destAccount, amount, reason);
        return 0;
    }

    // Possible code paths:
    // REQUIRED = {}
    // OPTIONAL = [command|subcommand]
    /////////////////////////////////////////////////////////////////////////////
    /// @fn         HelpCommand
    ///
    /// @param[in]  player is player who ran command
    ///
    /// @param[in]  helpCommand is the argument list to be parsed
    ///
    /// @brief      Builds a dynamic help list to the player to see all command
    ///             options on the server
    /////////////////////////////////////////////////////////////////////////////
    public int HelpCommand(ServerPlayerEntity sourcePlayer, String helpCommandList) {
        System.out.println("Running help command");
        List<String> helpCommand = new ArrayList<String>(Arrays.asList(helpCommandList.split(" ")));

        if (helpCommand.size() == 0) {
            // General help command
            for (Entry<String, String> helpCmd : HELP_COMMANDS.entrySet()) {
                String cmd = msgFormatter.ColorMsg(helpCmd.getKey(), COLOR_CODE.GOLD);
                String details = msgFormatter.FormatMsg(helpCmd.getValue(), COLOR_CODE.BLUE, COLOR_CODE.GRAY);

                sourcePlayer.sendMessage(Text.of(cmd + " --> " + details));
                sourcePlayer.sendMessage(Text.of("\n"));
            }
        } else {
            // Iterate over list; verifying what commands are found
            List<String> unknownCmds = new ArrayList<String>();
            for (int i = 0; i < helpCommand.size(); i++) {
                String newHelpCmd = helpCommand.get(i);
                if (HELP_COMMANDS.containsKey(newHelpCmd)) {
                    // Print help for this specific command
                    String cmd = msgFormatter.ColorMsg(newHelpCmd, COLOR_CODE.GOLD);
                    String details = msgFormatter.FormatMsg(HELP_COMMANDS.get(newHelpCmd), COLOR_CODE.BLUE, COLOR_CODE.GRAY);

                    sourcePlayer.sendMessage(Text.of(cmd + " -> " + details));
                } else {
                    // Add to unknown list at end
                    unknownCmds.add(newHelpCmd);
                }
            }

            // After all prints have finished tell the player the commands they plugged in that we did not recognize
            if (unknownCmds.size() > 0) {
                String msg = msgFormatter.ColorMsg("The following commands do not exist or were mispelt: ", COLOR_CODE.RED);
                sourcePlayer.sendMessage(Text.of(msg + unknownCmds));
            }
        }
        return 0;
    }

    // Possible args:
    // /bank create {SAVINGS/CHECKING} [optional: alias]
    /////////////////////////////////////////////////////////////////////////////
    /// @fn         CreateAccount
    ///
    /// @param[in]  player is player who ran command
    ///
    /// @param[in]  accountArgs is the argument list to be parsed
    ///
    /// @brief      Creates a bank account
    /////////////////////////////////////////////////////////////////////////////
    public int CreateAccount(ServerPlayerEntity player, ACCOUNT_TYPE accountType) {
        String bank = BankManager.GetInstance().GetPlayerSelectedBank(player.getUuidAsString());
        System.out.println("Received bank: " + bank);

        if (bank != "") {
            BankManager.GetInstance().InitiateBankAccountCreation(bank, player, accountType);
        }
        return 0;
    }

    // Possible code paths for /bank create report
    // required = {ACCOUNT ID / ALIAS} or {ALL}
    /////////////////////////////////////////////////////////////////////////////
    /// @fn         GenerateAccountReport
    ///
    /// @param[in]  player is player who ran command
    ///
    /// @param[in]  reportArgs is the argument list to be parsed
    ///
    /// @brief      Creates a bank account(s) report
    /////////////////////////////////////////////////////////////////////////////
    public void GenerateAccountReport(ServerPlayerEntity player, List<String> reportArgs) {
        if (reportArgs.size() > 0) {
            String reportIdentifier = reportArgs.get(0).toLowerCase();

            // First path is just to run a report on all accounts this player is attached to:
            if (reportIdentifier == "all") {
                /* BANKMANAGER CALL HERE. LOOP LIKELY NEEDED */
            } else {
                /* BANKMANAGER CALL HERE */
            }
        } else {
            player.sendMessage(Text.of("Unrecognized report field data. Please run /bank help report for more information"));
        }
    }
}
