package fr.luc.gamingcore.database;

import fr.luc.gamingcore.Main;
import fr.luc.gamingcore.model.*;
import org.bukkit.Bukkit;

import java.sql.*;
import java.util.*;
import java.util.logging.Level;
import java.util.stream.Collectors;

/**
 * Référentiel (Repository) gérant la persistance et la synchronisation des données des guildes en base de données.
 */
public class GuildRepository {

    private final Main plugin;
    private final DatabaseManager databaseManager;

    /**
     * Constructeur du GuildRepository.
     *
     * @param plugin          Instance du plugin principal.
     * @param databaseManager Gestionnaire de connexion à la base de données.
     */
    public GuildRepository(Main plugin, DatabaseManager databaseManager) {
        this.plugin = plugin;
        this.databaseManager = databaseManager;
    }

    /**
     * Charge l'intégralité des guildes, de leurs rangs, membres et claims au démarrage du serveur.
     *
     * @return Une Map associant l'UUID d'une guilde à son instance Guild chargée en mémoire.
     */
    public Map<UUID, Guild> loadAllGuilds() {
        Map<UUID, Guild> guilds = new HashMap<>();

        try (Connection conn = databaseManager.getConnection()) {
            // 1. Charger les guildes
            String sqlGuilds = "SELECT * FROM guilds";
            try (Statement stmt = conn.createStatement();
                 ResultSet rs = stmt.executeQuery(sqlGuilds)) {
                while (rs.next()) {
                    UUID id = UUID.fromString(rs.getString("id"));
                    String name = rs.getString("name");
                    String tag = rs.getString("tag");
                    String color = "#00C9FF";
                    try {
                        color = rs.getString("color");
                        if (color == null || color.isEmpty()) color = "#00C9FF";
                    } catch (SQLException ignored) {}
                    String description = rs.getString("description");
                    double balance = rs.getDouble("balance");
                    long createdAt = rs.getLong("created_at");

                    Guild guild = new Guild(id, name, tag, color, description, balance, createdAt);

                    String homeWorld = rs.getString("home_world");
                    if (homeWorld != null && !homeWorld.isEmpty()) {
                        guild.setHomeCoordinates(
                                homeWorld,
                                rs.getDouble("home_x"),
                                rs.getDouble("home_y"),
                                rs.getDouble("home_z"),
                                rs.getFloat("home_yaw"),
                                rs.getFloat("home_pitch")
                        );
                    }

                    guilds.put(id, guild);
                }
            }

            // 2. Charger les rangs
            String sqlRanks = "SELECT * FROM guild_ranks";
            try (Statement stmt = conn.createStatement();
                 ResultSet rs = stmt.executeQuery(sqlRanks)) {
                while (rs.next()) {
                    String rankId = rs.getString("id");
                    UUID guildId = UUID.fromString(rs.getString("guild_id"));
                    String name = rs.getString("name");
                    int priority = rs.getInt("priority");
                    boolean isDefault = rs.getInt("is_default") == 1;
                    String permsRaw = rs.getString("permissions");

                    Set<GuildPermission> permissions = new HashSet<>();
                    if (permsRaw != null && !permsRaw.isEmpty()) {
                        for (String permName : permsRaw.split(",")) {
                            try {
                                permissions.add(GuildPermission.valueOf(permName.trim().toUpperCase()));
                            } catch (IllegalArgumentException ignored) {}
                        }
                    }

                    Guild guild = guilds.get(guildId);
                    if (guild != null) {
                        GuildRank rank = new GuildRank(rankId, guildId, name, priority, isDefault, permissions);
                        guild.addRank(rank);
                    }
                }
            }

            // 3. Charger les membres
            String sqlMembers = "SELECT * FROM guild_members";
            try (Statement stmt = conn.createStatement();
                 ResultSet rs = stmt.executeQuery(sqlMembers)) {
                while (rs.next()) {
                    UUID playerUuid = UUID.fromString(rs.getString("player_uuid"));
                    UUID guildId = UUID.fromString(rs.getString("guild_id"));
                    String rankId = rs.getString("rank_id");
                    long joinedAt = rs.getLong("joined_at");
                    long lastOnline = rs.getLong("last_online");
                    double contributedMoney = rs.getDouble("contributed_money");

                    Guild guild = guilds.get(guildId);
                    if (guild != null) {
                        GuildMember member = new GuildMember(playerUuid, guildId, rankId, joinedAt, lastOnline, contributedMoney);
                        guild.addMember(member);
                    }
                }
            }

            // 4. Charger les claims
            String sqlClaims = "SELECT * FROM guild_claims";
            try (Statement stmt = conn.createStatement();
                 ResultSet rs = stmt.executeQuery(sqlClaims)) {
                while (rs.next()) {
                    int id = rs.getInt("id");
                    UUID guildId = UUID.fromString(rs.getString("guild_id"));
                    String world = rs.getString("world");
                    int chunkX = rs.getInt("chunk_x");
                    int chunkZ = rs.getInt("chunk_z");
                    long claimedAt = rs.getLong("claimed_at");

                    Guild guild = guilds.get(guildId);
                    if (guild != null) {
                        GuildClaim claim = new GuildClaim(id, guildId, world, chunkX, chunkZ, claimedAt);
                        guild.addClaim(claim);
                    }
                }
            }

        } catch (SQLException e) {
            plugin.getLogger().log(Level.SEVERE, "Erreur lors du chargement des guildes depuis la base SQLite !", e);
        }

        return guilds;
    }

    /**
     * Sauvegarde ou met à jour les informations principales d'une guilde de façon asynchrone.
     *
     * @param guild La guilde à sauvegarder.
     */
    public void saveGuildAsync(Guild guild) {
        Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
            try {
                saveGuild(guild);
            } catch (SQLException e) {
                plugin.getLogger().log(Level.SEVERE, "Erreur lors de la sauvegarde de la guilde " + guild.getName(), e);
            }
        });
    }

    /**
     * Sauvegarde synchrone des données principales d'une guilde.
     *
     * @param guild La guilde à insérer ou mettre à jour.
     * @throws SQLException En cas d'échec SQL.
     */
    public void saveGuild(Guild guild) throws SQLException {
        String sql = "INSERT OR REPLACE INTO guilds (id, name, tag, color, description, balance, home_world, home_x, home_y, home_z, home_yaw, home_pitch, created_at) " +
                "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
        try (PreparedStatement ps = databaseManager.getConnection().prepareStatement(sql)) {
            ps.setString(1, guild.getId().toString());
            ps.setString(2, guild.getName());
            ps.setString(3, guild.getTag());
            ps.setString(4, guild.getColor());
            ps.setString(5, guild.getDescription());
            ps.setDouble(6, guild.getBalance());
            ps.setString(7, guild.getHomeWorld());
            ps.setDouble(8, guild.getHomeX());
            ps.setDouble(9, guild.getHomeY());
            ps.setDouble(10, guild.getHomeZ());
            ps.setFloat(11, guild.getHomeYaw());
            ps.setFloat(12, guild.getHomePitch());
            ps.setLong(13, guild.getCreatedAt());
            ps.executeUpdate();
        }
    }

    /**
     * Supprime définitivement une guilde et toutes ses données associées en cascade de manière asynchrone.
     *
     * @param guildId Le UUID de la guilde à supprimer.
     */
    public void deleteGuildAsync(UUID guildId) {
        Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
            String sql = "DELETE FROM guilds WHERE id = ?";
            try (PreparedStatement ps = databaseManager.getConnection().prepareStatement(sql)) {
                ps.setString(1, guildId.toString());
                ps.executeUpdate();
            } catch (SQLException e) {
                plugin.getLogger().log(Level.SEVERE, "Erreur lors de la suppression de la guilde " + guildId, e);
            }
        });
    }

    /**
     * Sauvegarde un rang de guilde de façon asynchrone.
     *
     * @param rank Le rang à sauvegarder.
     */
    public void saveRankAsync(GuildRank rank) {
        Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
            String sql = "INSERT OR REPLACE INTO guild_ranks (id, guild_id, name, priority, is_default, permissions) " +
                    "VALUES (?, ?, ?, ?, ?, ?)";
            try (PreparedStatement ps = databaseManager.getConnection().prepareStatement(sql)) {
                ps.setString(1, rank.getId());
                ps.setString(2, rank.getGuildId().toString());
                ps.setString(3, rank.getName());
                ps.setInt(4, rank.getPriority());
                ps.setInt(5, rank.isDefault() ? 1 : 0);
                String permsStr = rank.getPermissions().stream().map(Enum::name).collect(Collectors.joining(","));
                ps.setString(6, permsStr);
                ps.executeUpdate();
            } catch (SQLException e) {
                plugin.getLogger().log(Level.SEVERE, "Erreur lors de la sauvegarde du rang " + rank.getName(), e);
            }
        });
    }

    /**
     * Supprime un rang de guilde de façon asynchrone.
     *
     * @param rankId  L'identifiant du rang.
     * @param guildId L'UUID de la guilde.
     */
    public void deleteRankAsync(String rankId, UUID guildId) {
        Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
            String sql = "DELETE FROM guild_ranks WHERE id = ? AND guild_id = ?";
            try (PreparedStatement ps = databaseManager.getConnection().prepareStatement(sql)) {
                ps.setString(1, rankId);
                ps.setString(2, guildId.toString());
                ps.executeUpdate();
            } catch (SQLException e) {
                plugin.getLogger().log(Level.SEVERE, "Erreur lors de la suppression du rang " + rankId, e);
            }
        });
    }

    /**
     * Sauvegarde un membre de guilde de façon asynchrone.
     *
     * @param member Le membre à sauvegarder.
     */
    public void saveMemberAsync(GuildMember member) {
        Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
            String sql = "INSERT OR REPLACE INTO guild_members (player_uuid, guild_id, rank_id, joined_at, last_online, contributed_money) " +
                    "VALUES (?, ?, ?, ?, ?, ?)";
            try (PreparedStatement ps = databaseManager.getConnection().prepareStatement(sql)) {
                ps.setString(1, member.getPlayerUuid().toString());
                ps.setString(2, member.getGuildId().toString());
                ps.setString(3, member.getRankId());
                ps.setLong(4, member.getJoinedAt());
                ps.setLong(5, member.getLastOnline());
                ps.setDouble(6, member.getContributedMoney());
                ps.executeUpdate();
            } catch (SQLException e) {
                plugin.getLogger().log(Level.SEVERE, "Erreur lors de la sauvegarde du membre " + member.getPlayerUuid(), e);
            }
        });
    }

    /**
     * Supprime un membre de guilde de façon asynchrone.
     *
     * @param playerUuid Le UUID du joueur à supprimer de la table.
     */
    public void deleteMemberAsync(UUID playerUuid) {
        Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
            String sql = "DELETE FROM guild_members WHERE player_uuid = ?";
            try (PreparedStatement ps = databaseManager.getConnection().prepareStatement(sql)) {
                ps.setString(1, playerUuid.toString());
                ps.executeUpdate();
            } catch (SQLException e) {
                plugin.getLogger().log(Level.SEVERE, "Erreur lors de la suppression du membre " + playerUuid, e);
            }
        });
    }

    /**
     * Insère un nouveau claim de territoire en base de données et met à jour son identifiant généré.
     *
     * @param claim Le claim à insérer.
     */
    public void saveClaimAsync(GuildClaim claim) {
        Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
            String sql = "INSERT INTO guild_claims (guild_id, world, chunk_x, chunk_z, claimed_at) VALUES (?, ?, ?, ?, ?)";
            try (PreparedStatement ps = databaseManager.getConnection().prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
                ps.setString(1, claim.getGuildId().toString());
                ps.setString(2, claim.getWorldName());
                ps.setInt(3, claim.getChunkX());
                ps.setInt(4, claim.getChunkZ());
                ps.setLong(5, claim.getClaimedAt());
                ps.executeUpdate();

                try (ResultSet rs = ps.getGeneratedKeys()) {
                    if (rs.next()) {
                        claim.setId(rs.getInt(1));
                    }
                }
            } catch (SQLException e) {
                plugin.getLogger().log(Level.SEVERE, "Erreur lors de la sauvegarde du claim " + claim, e);
            }
        });
    }

    /**
     * Supprime un claim de territoire de la base de données selon ses coordonnées de chunk de façon asynchrone.
     *
     * @param world  Le nom du monde.
     * @param chunkX L'abscisse du chunk.
     * @param chunkZ L'ordonnée du chunk.
     */
    public void deleteClaimAsync(String world, int chunkX, int chunkZ) {
        Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
            String sql = "DELETE FROM guild_claims WHERE world = ? AND chunk_x = ? AND chunk_z = ?";
            try (PreparedStatement ps = databaseManager.getConnection().prepareStatement(sql)) {
                ps.setString(1, world);
                ps.setInt(2, chunkX);
                ps.setInt(3, chunkZ);
                ps.executeUpdate();
            } catch (SQLException e) {
                plugin.getLogger().log(Level.SEVERE, "Erreur lors de la suppression du claim " + world + ":" + chunkX + "," + chunkZ, e);
            }
        });
    }

    /**
     * Sauvegarde une entrée d'historique de guilde de façon asynchrone.
     *
     * @param log L'entrée de log à sauvegarder.
     */
    public void saveLogAsync(fr.luc.gamingcore.model.GuildLog log) {
        Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
            String sql = "INSERT INTO guild_logs (guild_id, actor_uuid, actor_name, action_type, details, timestamp) VALUES (?, ?, ?, ?, ?, ?)";
            try (PreparedStatement ps = databaseManager.getConnection().prepareStatement(sql)) {
                ps.setString(1, log.getGuildId().toString());
                ps.setString(2, log.getActorUuid() != null ? log.getActorUuid().toString() : null);
                ps.setString(3, log.getActorName());
                ps.setString(4, log.getActionType());
                ps.setString(5, log.getDetails());
                ps.setLong(6, log.getTimestamp());
                ps.executeUpdate();
            } catch (SQLException e) {
                plugin.getLogger().log(Level.SEVERE, "Erreur lors de la sauvegarde du log de guilde", e);
            }
        });
    }

    /**
     * Récupère de façon asynchrone les dernières entrées de log d'une guilde.
     *
     * @param guildId  L'UUID de la guilde.
     * @param limit    Nombre maximum d'entrées à récupérer.
     * @param callback Callback recevant la liste des logs ordonnée du plus récent au plus ancien.
     */
    public void getLogsAsync(UUID guildId, int limit, java.util.function.Consumer<java.util.List<fr.luc.gamingcore.model.GuildLog>> callback) {
        Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
            java.util.List<fr.luc.gamingcore.model.GuildLog> list = new java.util.ArrayList<>();
            String sql = "SELECT id, guild_id, actor_uuid, actor_name, action_type, details, timestamp FROM guild_logs WHERE guild_id = ? ORDER BY timestamp DESC LIMIT ?";
            try (PreparedStatement ps = databaseManager.getConnection().prepareStatement(sql)) {
                ps.setString(1, guildId.toString());
                ps.setInt(2, limit);
                try (ResultSet rs = ps.executeQuery()) {
                    while (rs.next()) {
                        long id = rs.getLong("id");
                        String actorUuidStr = rs.getString("actor_uuid");
                        UUID actorUuid = actorUuidStr != null ? UUID.fromString(actorUuidStr) : null;
                        String actorName = rs.getString("actor_name");
                        String actionType = rs.getString("action_type");
                        String details = rs.getString("details");
                        long timestamp = rs.getLong("timestamp");

                        list.add(new fr.luc.gamingcore.model.GuildLog(id, guildId, actorUuid, actorName, actionType, details, timestamp));
                    }
                }
            } catch (SQLException e) {
                plugin.getLogger().log(Level.SEVERE, "Erreur lors du chargement des logs de la guilde " + guildId, e);
            }
            Bukkit.getScheduler().runTask(plugin, () -> callback.accept(list));
        });
    }
}
