/*
 *   This file is a part of the SteamWar software.
 *
 *   Copyright (C) 2025 SteamWar.de-Serverteam
 *
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU Affero General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *   GNU Affero General Public License for more details.
 *
 *   You should have received a copy of the GNU Affero General Public License
 *   along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

package de.steamwar.lobby.boatrace;

import de.steamwar.entity.REntity;
import de.steamwar.entity.REntityServer;
import de.steamwar.entity.RInteraction;
import de.steamwar.lobby.LobbySystem;
import de.steamwar.lobby.util.LeaderboardManager;
import de.steamwar.sql.SteamwarUser;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Sound;
import org.bukkit.boss.BarColor;
import org.bukkit.boss.BarStyle;
import org.bukkit.boss.BossBar;
import org.bukkit.entity.Boat;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.entity.boat.*;
import org.bukkit.event.EventHandler;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
import org.bukkit.event.vehicle.VehicleExitEvent;
import org.bukkit.event.vehicle.VehicleMoveEvent;
import org.bukkit.scheduler.BukkitTask;

import java.util.EventListener;
import java.util.Random;

import static de.steamwar.lobby.util.LeaderboardManager.renderTime;

public class BoatRace implements EventListener, Listener {

    private static final String CONFIG_KEY = "lobby@boatrace";

    private static final double MIN_HEIGHT = 4.3;

    public static final REntityServer boatNpcServer;

    private static boolean oneNotStarted = false;
    private static final LeaderboardManager leaderboard;

    static {
        boatNpcServer = new REntityServer();
        new REntity(boatNpcServer, EntityType.VILLAGER, BoatRacePositions.NPC);
        RInteraction interaction = new RInteraction(boatNpcServer, BoatRacePositions.NPC.clone());
        interaction.setInteractionHeight(1.95f);
        interaction.setCallback((player, entity, action) -> {
            Bukkit.getWorlds().get(0).getEntities().stream().filter(e -> e.getType() == EntityType.END_CRYSTAL).forEach(Entity::remove);
            if (!oneNotStarted) {
                oneNotStarted = true;
                new BoatRace(player);
            }
        });
        leaderboard = new LeaderboardManager(boatNpcServer, CONFIG_KEY, BoatRacePositions.LEADERBOARD);
    }

    private final Player player;
    private Boat boat;
    private int nextCheckpoint = 0;
    private long startTime;
    private final BukkitTask task;
    private final BossBar bossBar;
    private boolean hasBacked = false;
    private double lastDistance;

    @EventHandler
    public void onBoatMove(VehicleMoveEvent event) {
        if (event.getVehicle() != boat) return;

        lastDistance = event.getFrom().distance(event.getTo());

        if (nextCheckpoint == 0 && inRegion(player, BoatRacePositions.BACKWARDS[0], BoatRacePositions.BACKWARDS[1])) {
            player.eject();
            player.teleport(BoatRacePositions.END);
            oneNotStarted = false;
            return;
        }

        if (player.getLocation().getY() < MIN_HEIGHT) {
            Location[] backTo = BoatRacePositions.CHECKPOINTS[nextCheckpoint - 1];
            Location avg = new Location(backTo[0].getWorld(), (backTo[0].getX() + backTo[1].getX()) / 2, Math.max(backTo[0].getY(), backTo[1].getY()), (backTo[0].getZ() + backTo[1].getZ()) / 2, backTo[0].getYaw(), backTo[0].getPitch());
            Boat nboat = Bukkit.getWorlds().get(0).spawn(avg, Boat.class);
            nboat.setBoatType(boat.getBoatType());
            hasBacked = true;
            player.eject();
            boat.remove();
            boat = nboat;
            boat.addPassenger(player);
            player.playSound(avg, Sound.ENTITY_ENDERMAN_TELEPORT, 1, 1);
            return;
        }

        Location[] checkpoint = BoatRacePositions.CHECKPOINTS[nextCheckpoint];
        if (inRegion(player, checkpoint[0], checkpoint[1])) {
            if (nextCheckpoint == 0) {
                oneNotStarted = false;
                startTime = System.currentTimeMillis();
                bossBar.addPlayer(player);
                bossBar.setVisible(true);
            }
            nextCheckpoint++;
            if (nextCheckpoint == BoatRacePositions.CHECKPOINTS.length) {
                long time = System.currentTimeMillis() - startTime;
                boat.remove();
                player.eject();
                player.teleport(BoatRacePositions.END);
                player.playSound(player.getLocation(), Sound.UI_TOAST_CHALLENGE_COMPLETE, 1, 1);
                bossBar.removeAll();
                HandlerList.unregisterAll(this);
                task.cancel();
                LobbySystem.getMessage().send("BOAT_RACE_TIME", player, renderTime(time));
                SteamwarUser user = SteamwarUser.get(player.getUniqueId());
                long best = leaderboard.getPlayerTime(user);
                if (time < best) {
                    LobbySystem.getMessage().send("BOAT_RACE_NEW_BEST", player);
                    leaderboard.updateBestTime(user, time);
                }
            } else {
                player.playSound(player.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 1, 1);
            }
        }
    }

    @EventHandler
    public void onVehicleExit(VehicleExitEvent event) {
        if (event.getVehicle() != boat) return;
        if (event.getExited() != player) return;
        if (hasBacked) return;
        HandlerList.unregisterAll(this);
        task.cancel();
        bossBar.removeAll();
        boat.remove();
        player.teleport(BoatRacePositions.END);
        oneNotStarted = false;
    }

    private final Class<? extends Boat>[] boatClasses = new Class[]{
            AcaciaBoat.class,
            BambooRaft.class,
            BirchBoat.class,
            CherryBoat.class,
            DarkOakBoat.class,
            JungleBoat.class,
            MangroveBoat.class,
            OakBoat.class,
            PaleOakBoat.class,
            SpruceBoat.class,
    };

    private final Random random = new Random();

    public BoatRace(Player player) {
        this.player = player;
        boat = Bukkit.getWorlds().get(0).spawn(BoatRacePositions.START, boatClasses[random.nextInt(boatClasses.length)]);
        boat.addPassenger(player);
        bossBar = Bukkit.createBossBar("", BarColor.BLUE, BarStyle.SOLID);
        task = Bukkit.getScheduler().runTaskTimer(LobbySystem.getInstance(), () -> {
            hasBacked = false;
            if (nextCheckpoint != 0) {
                double kmh = lastDistance * 20 * 3.6;
                bossBar.setProgress((nextCheckpoint - 1d) / (BoatRacePositions.CHECKPOINTS.length - 1d));
                bossBar.setTitle(LobbySystem.getMessage().parse("BOAT_RACE_TITLE", player, nextCheckpoint, renderTime(System.currentTimeMillis() - startTime), (int) kmh));
            }
        }, 0, 1);
        Bukkit.getPluginManager().registerEvents(this, LobbySystem.getInstance());
    }

    private boolean inRegion(Player p, Location loc1, Location loc2) {
        double x1 = Math.min(loc1.getX(), loc2.getX());
        double y1 = Math.min(loc1.getY(), loc2.getY());
        double z1 = Math.min(loc1.getZ(), loc2.getZ());

        double x2 = Math.max(loc1.getX(), loc2.getX()) + 1;
        double y2 = Math.max(loc1.getY(), loc2.getY()) + 1;
        double z2 = Math.max(loc1.getZ(), loc2.getZ()) + 1;

        return p.getLocation().getX() >= x1 && p.getLocation().getY() >= y1 && p.getLocation().getZ() >= z1 && p.getLocation().getX() < x2 && p.getLocation().getY() < y2 && p.getLocation().getZ() < z2;
    }
}
