package dev.micle.loginprotection.data;

import dev.micle.loginprotection.LoginProtection;
import dev.micle.loginprotection.network.NetworkManager;
import dev.micle.loginprotection.network.server.PlayerStatePacket;
import dev.micle.loginprotection.network.server.RequestLastInputTickPacket;
import dev.micle.loginprotection.setup.Config;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.effect.MobEffectInstance;
import net.minecraft.world.effect.MobEffects;
import net.minecraft.world.entity.player.Player;
import net.minecraftforge.network.NetworkDirection;

import java.util.ArrayList;
import java.util.List;
import java.util.TimerTask;
import java.util.UUID;

public class ProtectedPlayerManager {
    // Initialize variables
    private static final List<ProtectedPlayer> protectedPlayers = new ArrayList<>();

    /**
     * Method for initializing the ProtectedPlayerManager.
     */
    public static void init() {
        for (ProtectedPlayer protectedPlayer : protectedPlayers) {
            removePlayer(protectedPlayer.getPlayerUUID());
        }
    }

    /**
     * Adds a player to be protected.
     * @param playerUUID UUID of player to protect.
     */
    public static void addPlayer(UUID playerUUID) {
        if (getPlayer(playerUUID) != null) {
            return;
        }
        try {
            protectedPlayers.add(new ProtectedPlayer(playerUUID, ProtectedPlayer.State.JOINING));
            NetworkManager.getChannel().sendTo(new PlayerStatePacket(ProtectedPlayer.State.JOINING),
                    LoginProtection.getProxy().getServer().getPlayerList().getPlayer(playerUUID).connection.getConnection(),
                    NetworkDirection.PLAY_TO_CLIENT);
        } catch (NullPointerException ignored) {}
    }

    /**
     * Gets a protected player from the list of protected players.
     * @param playerUUID UUID of player to get.
     * @return ProtectedPlayer instance if player is present, otherwise null.
     */
    public static ProtectedPlayer getPlayer(UUID playerUUID) {
        for (ProtectedPlayer protectedPlayer : protectedPlayers) {
            if (protectedPlayer.getPlayerUUID().equals(playerUUID)) {
                return protectedPlayer;
            }
        }
        return null;
    }

    /**
     * Removes a player from the list of protected players.
     * @param playerUUID UUID of player to remove.
     */
    public static void removePlayer(UUID playerUUID) {
        ProtectedPlayer player = getPlayer(playerUUID);
        if (player == null) {
            return;
        }

        player.getTimer().cancel();
        protectedPlayers.remove(player);
    }

    /**
     * Updates a player's state appropriately.
     * @param playerUUID UUID of player to update the state of.
     */
    public static void updateState(UUID playerUUID) {
        ProtectedPlayer protectedPlayer = getPlayer(playerUUID);
        ServerPlayer player = LoginProtection.getProxy().getServer().getPlayerList().getPlayer(playerUUID);
        if (player == null) {
            removePlayer(playerUUID);
            return;
        }

        if (protectedPlayer == null) {
            addPlayer(playerUUID);
        } else {
            ProtectedPlayer.State currentState = protectedPlayer.getState();
            if (currentState.equals(ProtectedPlayer.State.JOINING)) {
                if (Config.Server.LOGIN_GRACE_ENABLED.get()) {
                    // JOINING -> LOGIN_GRACE
                    protectedPlayer.setState(ProtectedPlayer.State.LOGIN_GRACE);
                    startGraceTimer(playerUUID, Config.Server.LOGIN_GRACE_DURATION.get() * 1000);
                } else {
                    // JOINING -> ACTIVE
                    protectedPlayer.setState(ProtectedPlayer.State.ACTIVE);
                    if (Config.Server.LOGIN_APPLY_POST_EFFECTS.get()) {
                        applyPostEffects(playerUUID);
                    }
                    if (Config.Server.AFK_PROTECTION_ENABLED.get()) {
                        startAfkTimer(playerUUID, Config.Server.AFK_TIME_THRESHOLD.get() * 1000);
                    } else {
                        removePlayer(playerUUID);
                    }
                }
            } else if (currentState.equals(ProtectedPlayer.State.LOGIN_GRACE) ||
                    currentState.equals(ProtectedPlayer.State.AFK_GRACE)) {
                // LOGIN_GRACE, AFK_GRACE -> ACTIVE
                protectedPlayer.setState(ProtectedPlayer.State.ACTIVE);
                if ((currentState.equals(ProtectedPlayer.State.LOGIN_GRACE) && Config.Server.LOGIN_APPLY_POST_EFFECTS.get()) ||
                        (currentState.equals(ProtectedPlayer.State.AFK_GRACE) && Config.Server.AFK_APPLY_POST_EFFECTS.get())) {
                    applyPostEffects(playerUUID);
                }
                if (Config.Server.AFK_PROTECTION_ENABLED.get()) {
                    startAfkTimer(playerUUID, Config.Server.AFK_TIME_THRESHOLD.get() * 1000);
                } else {
                    removePlayer(playerUUID);
                }
            } else if (currentState.equals(ProtectedPlayer.State.ACTIVE)) {
                // ACTIVE -> AFK
                protectedPlayer.setState(ProtectedPlayer.State.AFK);
            } else if (currentState.equals(ProtectedPlayer.State.AFK)) {
                if (Config.Server.AFK_GRACE_ENABLED.get()) {
                    // AFK -> AFK_GRACE
                    protectedPlayer.setState(ProtectedPlayer.State.AFK_GRACE);
                    startGraceTimer(playerUUID, Config.Server.AFK_GRACE_DURATION.get() * 1000);
                } else {
                    // AFK -> ACTIVE
                    protectedPlayer.setState(ProtectedPlayer.State.ACTIVE);
                    if (Config.Server.AFK_APPLY_POST_EFFECTS.get()) {
                        applyPostEffects(playerUUID);
                    }
                    if (Config.Server.AFK_PROTECTION_ENABLED.get()) {
                        startAfkTimer(playerUUID, Config.Server.AFK_TIME_THRESHOLD.get() * 1000);
                    } else {
                        removePlayer(playerUUID);
                    }
                }
            }

            // Send state packet to player
            NetworkManager.getChannel().sendTo(new PlayerStatePacket(protectedPlayer.getState()),
                    player.connection.getConnection(),
                    NetworkDirection.PLAY_TO_CLIENT);
        }
    }

    /**
     * Starts the afk timer for a given player if they are a protected player.
     * @param playerUUID The UUID of the player.
     * @param delay After how much time should the task run? (in milliseconds)
     */
    public static void startAfkTimer(UUID playerUUID, long delay) {
        ProtectedPlayer player = getPlayer(playerUUID);
        if (player == null) {
            return;
        }

        // Create scheduled task
        player.setAfkTimerTask(new TimerTask() {
            @Override
            public void run() {
                try {
                    // Send request for list input tick packet to player
                    NetworkManager.getChannel().sendTo(new RequestLastInputTickPacket(),
                            LoginProtection.getProxy().getServer().getPlayerList().getPlayer(playerUUID).connection.getConnection(),
                            NetworkDirection.PLAY_TO_CLIENT);
                } catch (NullPointerException e) {
                    removePlayer(playerUUID);
                }
            }
        }, delay);
    }

    /**
     * Starts the grace period timer for a given player if they are a protected player.
     * @param playerUUID UUID of the player.
     * @param delay How long should the grace period last? (in milliseconds)
     */
    private static void startGraceTimer(UUID playerUUID, long delay) {
        ProtectedPlayer player = getPlayer(playerUUID);
        if (player == null) {
            return;
        }

        // Create scheduled task
        player.setGracePeriodTimerTask(new TimerTask() {
            @Override
            public void run() {
                // Update player state
                updateState(playerUUID);
            }
        }, delay);
    }

    /**
     * Applies effects to the player.
     * @param playerUUID UUID of player to apply effects to.
     */
    private static void applyPostEffects(UUID playerUUID) {
        // Get player entity
        Player player = LoginProtection.getProxy().getServer().getPlayerList().getPlayer(playerUUID);
        if (player == null) {
            removePlayer(playerUUID);
            return;
        }

        // Apply effects
        if (player.isInWater()) {
            if (Config.Server.POST_REFILL_AIR_ENABLED.get()) {
                player.setAirSupply(player.getMaxAirSupply());
            }
            if (Config.Server.POST_WATER_ENABLED.get()) {
                player.addEffect(new MobEffectInstance(MobEffects.WATER_BREATHING,
                        Config.Server.POST_WATER_DURATION.get() * 20, 0));
            }
        }
        if (player.isInLava()) {
            if (Config.Server.POST_LAVA_ENABLED.get()) {
                player.addEffect(new MobEffectInstance(MobEffects.FIRE_RESISTANCE,
                        Config.Server.POST_LAVA_DURATION.get()*20, 0));
            }
        }
        if (player.isOnFire()) {
            if (Config.Server.POST_FIRE_ENABLED.get()) {
                player.addEffect(new MobEffectInstance(MobEffects.FIRE_RESISTANCE,
                        Config.Server.POST_FIRE_DURATION.get()*20, 0));
            }
        }
    }
}
