/*
 *   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.util;

import de.steamwar.entity.RArmorStand;
import de.steamwar.entity.REntity;
import de.steamwar.entity.REntityServer;
import de.steamwar.lobby.LobbySystem;
import de.steamwar.sql.SteamwarUser;
import de.steamwar.sql.internal.Statement;
import lombok.AllArgsConstructor;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;

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

public class Leaderboard implements Listener {

    private static final Statement LEADERBOARD = new Statement("SELECT User, CAST(Value as integer) AS Time from UserConfig WHERE Config = ? ORDER BY CAST(Value as integer) ASC LIMIT ?");
    private static final Statement PLAYER_TIME = new Statement("SELECT CAST(Value as integer) AS Time FROM UserConfig WHERE Config = ? AND User = ?");
    private static final Statement PLAYER_PLACEMENT = new Statement("SELECT COUNT(*) AS Placement FROM UserConfig WHERE Config = ? AND CAST(Value as integer) < (SELECT CAST(Value as integer) AS Time FROM UserConfig WHERE Config = ? AND User = ?)");

    private final REntityServer server;
    private final String configKey;
    private final Location location;
    private final int best;
    private long bestTime;
    private final List<REntity> entities = new ArrayList<>();
    private final Map<Integer, REntityServer> playerPlacements = new HashMap<>();

    public Leaderboard(REntityServer server, String configKey, Location location, int best) {
        this.server = server;
        this.configKey = configKey;
        this.location = location;
        this.best = best;
        Bukkit.getPluginManager().registerEvents(this, LobbySystem.getInstance());
        update();
    }

    public void update() {
        entities.forEach(REntity::die);
        entities.clear();
        List<LeaderboardEntry> leaderboard = getLeaderboard();
        if (leaderboard.isEmpty()) return;
        bestTime = leaderboard.get(0).time;
        for (int i = 0; i < leaderboard.size(); i++) {
            LeaderboardEntry entry = leaderboard.get(i);
            RArmorStand entity = new RArmorStand(server, location.clone().add(0, (leaderboard.size() - i - 1) * 0.32, 0), RArmorStand.Size.MARKER);
            SteamwarUser user = SteamwarUser.get(entry.user);
            String color = "§7";
            if (i == 0) {
                color = "§6§l";
            } else if (i < 3) {
                color = "§e";
            }
            entity.setDisplayName(calcName(user, color, i + 1, entry.time));
            entity.setInvisible(true);
            entities.add(entity);
        }
        Bukkit.getOnlinePlayers().forEach(player -> {
            updatePlayerPlacement(player, SteamwarUser.get(player.getUniqueId()));
        });
    }

    private void updatePlayerPlacement(Player player, SteamwarUser steamwarUser) {
        if (server.getEntities().isEmpty()) return;
        int placement = getPlayerPlacement(steamwarUser);
        if (placement < 5) {
            REntityServer entityServer = playerPlacements.remove(steamwarUser.getId());
            if (entityServer == null) return;
            entityServer.getEntities().forEach(rEntity -> rEntity.hide(true));
            return;
        }
        REntityServer entityServer = playerPlacements.computeIfAbsent(steamwarUser.getId(), ignore -> {
            REntityServer server = new REntityServer();
            server.addPlayer(player);
            return server;
        });
        List<REntity> entities = entityServer.getEntities();
        RArmorStand entity;
        if (entities.isEmpty()) {
            entity = new RArmorStand(entityServer, location.clone().add(0, -2 * 0.32, 0), RArmorStand.Size.MARKER);
            entity.setInvisible(true);
        } else {
            entity = (RArmorStand) entities.get(0);
            entity.hide(false);
        }
        long time = getPlayerTime(steamwarUser);
        entity.setDisplayName(calcName(steamwarUser, "§f", placement, time));
    }

    private String calcName(SteamwarUser user, String color, int placement, long time) {
        StringBuilder st = new StringBuilder();
        st.append(color);
        if (placement == Integer.MAX_VALUE) {
            st.append("???").append(". ").append(user.getUserName());
        } else {
            st.append(placement).append(". ").append(user.getUserName());
        }
        st.append(" §8• ").append(color);
        if (placement == Integer.MAX_VALUE) {
            st.append("??:??,???");
        } else {
            st.append(renderTime(time));
        }
        if (time != bestTime && placement != Integer.MAX_VALUE) {
            st.append(" §8• ");
            st.append(color).append("+").append(renderShortTime(time - bestTime));
        }
        return st.toString();
    }

    private List<LeaderboardEntry> getLeaderboard() {
        return LEADERBOARD.select(resultSet -> {
            List<LeaderboardEntry> leaderboard = new ArrayList<>();
            while (resultSet.next()) {
                leaderboard.add(new LeaderboardEntry(resultSet.getInt("User"), resultSet.getLong("Time")));
            }
            return leaderboard;
        }, configKey, best);
    }

    private long getPlayerTime(SteamwarUser user) {
        return PLAYER_TIME.select(resultSet -> {
            if (!resultSet.next()) {
                return Long.MAX_VALUE;
            }
            return resultSet.getLong("Time");
        }, configKey, user.getId());
    }

    private int getPlayerPlacement(SteamwarUser user) {
        return PLAYER_PLACEMENT.select(resultSet -> {
            if (!resultSet.next()) {
                return Integer.MAX_VALUE;
            }
            return resultSet.getInt("Placement");
        }, configKey, configKey, user.getId());
    }

    public static String renderTime(long time) {
        return String.format(
                "%d:%02d.%03d",
                time / 60000,
                (time / 1000) % 60,
                time % 1000);
    }

    public static String renderShortTime(long time) {
        return String.format(
                "%d.%03d",
                time / 1000,
                time % 1000);
    }

    @AllArgsConstructor
    private class LeaderboardEntry {

        private final int user;
        private final long time;
    }

    @EventHandler
    public void onPlayerJoin(PlayerJoinEvent event) {
        SteamwarUser steamwarUser = SteamwarUser.get(event.getPlayer().getUniqueId());
        updatePlayerPlacement(event.getPlayer(), steamwarUser);
    }

    @EventHandler
    public void onPlayerQuit(PlayerQuitEvent event) {
        SteamwarUser steamwarUser = SteamwarUser.get(event.getPlayer().getUniqueId());
        REntityServer entityServer = playerPlacements.remove(steamwarUser.getId());
        if (entityServer == null) return;
        entityServer.close();
    }
}
