package emu.grasscutter.command.commands;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.avatar.Avatar;
import emu.grasscutter.game.entity.EntityAvatar;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify;

@Command(
    label = "setStats",
    aliases = {"stats", "stat"},
    usage = {
        "[set] <stat> <value>",
        "(lock|freeze) <stat> [<value>]",  // Can lock to current value
        "(unlock|unfreeze) <stat>"},
    permission = "player.setstats",
    permissionTargeted = "player.setstats.others")
public final class SetStatsCommand implements CommandHandler {
    private static class Stat {
        String name;
        FightProperty prop;

        public Stat(FightProperty prop) {
            this.name = prop.toString();
            this.prop = prop;
        }

        public Stat(String name, FightProperty prop) {
            this.name = name;
            this.prop = prop;
        }
    }

    private static enum Action {
        ACTION_SET("commands.generic.set_to", "commands.generic.set_for_to"),
        ACTION_LOCK("commands.setStats.locked_to", "commands.setStats.locked_for_to"),
        ACTION_UNLOCK("commands.setStats.unlocked", "commands.setStats.unlocked_for");
        public final String messageKeySelf;
        public final String messageKeyOther;
        private Action(String messageKeySelf, String messageKeyOther) {
            this.messageKeySelf = messageKeySelf;
            this.messageKeyOther = messageKeyOther;
        }
    }

    private Map<String, Stat> stats;

    public SetStatsCommand() {
        this.stats = new HashMap<>();
        for (String key : FightProperty.getShortNames()) {
            this.stats.put(key, new Stat(FightProperty.getPropByShortName(key)));
        }
        // Full FightProperty enum that won't be advertised but can be used by devs
        // They have a prefix to avoid the "hp" clash
        for (FightProperty prop : FightProperty.values()) {
            String name = prop.toString().substring(10);  // FIGHT_PROP_BASE_HP -> _BASE_HP
            String key = name.toLowerCase();  // _BASE_HP -> _base_hp
            name = name.substring(1);  // _BASE_HP -> BASE_HP
            this.stats.put(key, new Stat(name, prop));
        }

        // Compatibility aliases
        this.stats.put("mhp", this.stats.get("maxhp"));
        this.stats.put("hp", this.stats.get("_cur_hp"));  // Overrides FIGHT_PROP_HP
        this.stats.put("atk", this.stats.get("_cur_attack"));  // Overrides FIGHT_PROP_ATTACK
        this.stats.put("def", this.stats.get("_cur_defense"));  // Overrides FIGHT_PROP_DEFENSE
        this.stats.put("atkb", this.stats.get("_base_attack"));  // This doesn't seem to get used to recalculate ATK, so it's only useful for stuff like Bennett's buff.
        this.stats.put("eanemo", this.stats.get("anemo%"));
        this.stats.put("ecryo", this.stats.get("cryo%"));
        this.stats.put("edendro", this.stats.get("dendro%"));
        this.stats.put("edend", this.stats.get("dendro%"));
        this.stats.put("eelectro", this.stats.get("electro%"));
        this.stats.put("eelec", this.stats.get("electro%"));
        this.stats.put("ethunder", this.stats.get("electro%"));
        this.stats.put("egeo", this.stats.get("geo%"));
        this.stats.put("ehydro", this.stats.get("hydro%"));
        this.stats.put("epyro", this.stats.get("pyro%"));
        this.stats.put("ephys", this.stats.get("phys%"));
    }

    public static float parsePercent(String input) throws NumberFormatException {
        if (input.endsWith("%")) {
            return Float.parseFloat(input.substring(0, input.length()-1))/100f;
        } else {
            return Float.parseFloat(input);
        }
    }

    @Override
    public void execute(Player sender, Player targetPlayer, List<String> args) {
        String statStr = null;
        String valueStr;
        float value = 0f;

        if (args.size() < 2) {
            sendUsageMessage(sender);
            return;
        }

        // Get the action and stat
        String arg0 = args.remove(0).toLowerCase();
        Action action = switch (arg0) {
            default -> {statStr = arg0; yield Action.ACTION_SET;}  // Implicit set command
            case "set" -> Action.ACTION_SET;  // Explicit set command
            case "lock", "freeze" -> Action.ACTION_LOCK;
            case "unlock", "unfreeze" -> Action.ACTION_UNLOCK;
        };
        if (statStr == null) {
            statStr = args.remove(0).toLowerCase();
        }
        if (!stats.containsKey(statStr)) {
            sendUsageMessage(sender);  // Invalid stat or action
            return;
        }
        Stat stat = stats.get(statStr);
        EntityAvatar entity = targetPlayer.getTeamManager().getCurrentAvatarEntity();
        Avatar avatar = entity.getAvatar();

        // Get the value if the action requires it
        try {
            switch (action) {
                case ACTION_LOCK:
                    if (args.isEmpty()) {  // Lock to current value
                        value = avatar.getFightProperty(stat.prop);
                        break;
                    }  // Else fall-through and lock to supplied value
                case ACTION_SET:
                    value = parsePercent(args.remove(0));
                    break;
                case ACTION_UNLOCK:
                    break;
            }
        } catch (NumberFormatException ignored) {
            CommandHandler.sendTranslatedMessage(sender, "commands.generic.invalid.statValue");
            return;
        } catch (IndexOutOfBoundsException ignored) {
            sendUsageMessage(sender);
            return;
        }

        if (!args.isEmpty()) {  // Leftover arguments!
            sendUsageMessage(sender);
            return;
        }

        switch (action) {
            case ACTION_SET:
                entity.setFightProperty(stat.prop, value);
                entity.getWorld().broadcastPacket(new PacketEntityFightPropUpdateNotify(entity, stat.prop));
                break;
            case ACTION_LOCK:
                avatar.getFightPropOverrides().put(stat.prop.getId(), value);
                avatar.recalcStats();
                break;
            case ACTION_UNLOCK:
                avatar.getFightPropOverrides().remove(stat.prop.getId());
                avatar.recalcStats();
                break;
        }

        // Report action
        if (FightProperty.isPercentage(stat.prop)) {
            valueStr = String.format("%.1f%%", value * 100f);
        } else {
            valueStr = String.format("%.0f", value);
        }
        if (targetPlayer == sender) {
            CommandHandler.sendTranslatedMessage(sender, action.messageKeySelf, stat.name, valueStr);
        } else {
            String uidStr = targetPlayer.getAccount().getId();
            CommandHandler.sendTranslatedMessage(sender, action.messageKeyOther, stat.name, uidStr, valueStr);
        }
        return;
    }
}
