package fr.luc.gamingcore.model;

import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.World;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * Entité principale représentant une guilde complète.
 * Regroupe ses membres, ses rangs hiérarchiques, ses claims territoriaux et sa banque.
 */
public class Guild {

    private final UUID id;
    private String name;
    private String tag;
    private String color;
    private String description;
    private double balance;

    // Home de guilde
    private String homeWorld;
    private double homeX;
    private double homeY;
    private double homeZ;
    private float homeYaw;
    private float homePitch;

    private final long createdAt;

    // Collections
    private final Map<UUID, GuildMember> members = new ConcurrentHashMap<>();
    private final Map<String, GuildRank> ranks = new ConcurrentHashMap<>();
    private final List<GuildClaim> claims = new CopyOnWriteArrayList<>();

    /**
     * Constructeur complet d'une guilde avec couleur personnalisée.
     *
     * @param id          Identifiant unique UUID de la guilde.
     * @param name        Nom de la guilde (ex: "Imperium").
     * @param tag         Tag/Sigle court de la guilde (ex: "IMP").
     * @param color       Couleur principale en code hexadécimal (ex: "#00C9FF").
     * @param description Description textuelle de la guilde.
     * @param balance     Solde bancaire initial de la guilde.
     * @param createdAt   Date et heure de création en millisecondes.
     */
    public Guild(UUID id, String name, String tag, String color, String description, double balance, long createdAt) {
        this.id = Objects.requireNonNull(id, "L'UUID de la guilde ne peut pas être nul.");
        this.name = Objects.requireNonNull(name, "Le nom de la guilde ne peut pas être nul.");
        this.tag = tag != null ? tag : "";
        this.color = color != null && color.startsWith("#") ? color : "#00C9FF";
        this.description = description != null ? description : "Une nouvelle guilde prometteuse.";
        this.balance = Math.max(0.0, balance);
        this.createdAt = createdAt > 0 ? createdAt : System.currentTimeMillis();
    }

    /**
     * Constructeur surchargé utilisant la couleur par défaut.
     *
     * @param id          Identifiant unique UUID de la guilde.
     * @param name        Nom de la guilde.
     * @param tag         Tag court.
     * @param description Description.
     * @param balance     Solde bancaire.
     * @param createdAt   Timestamp de création.
     */
    public Guild(UUID id, String name, String tag, String description, double balance, long createdAt) {
        this(id, name, tag, "#00C9FF", description, balance, createdAt);
    }

    /**
     * Obtient l'identifiant unique UUID de la guilde.
     *
     * @return L'UUID de la guilde.
     */
    public UUID getId() {
        return id;
    }

    /**
     * Obtient le nom unique de la guilde.
     *
     * @return Le nom de la guilde.
     */
    public String getName() {
        return name;
    }

    /**
     * Modifie le nom de la guilde.
     *
     * @param name Le nouveau nom.
     */
    public void setName(String name) {
        this.name = Objects.requireNonNull(name, "Le nom ne peut pas être nul.");
    }

    /**
     * Obtient le tag / préfixe court de la guilde.
     *
     * @return Le tag (ex: "LEG").
     */
    public String getTag() {
        return tag;
    }

    /**
     * Modifie le tag court de la guilde.
     *
     * @param tag Le nouveau tag.
     */
    public void setTag(String tag) {
        this.tag = tag != null ? tag : "";
    }

    /**
     * Obtient la couleur hexadécimale de la guilde (ex: "#00C9FF").
     *
     * @return Le code couleur HEX.
     */
    public String getColor() {
        return color != null ? color : "#00C9FF";
    }

    /**
     * Modifie la couleur hexadécimale de la guilde.
     *
     * @param color Le code couleur hexadécimal (ex: "#FF5555").
     */
    public void setColor(String color) {
        if (color != null && color.startsWith("#")) {
            this.color = color.toUpperCase();
        }
    }

    /**
     * Obtient le tag formaté avec la couleur hexadécimale de la guilde (MiniMessage).
     *
     * @return Le tag stylisé (ex: "<color:#00C9FF>[IMP]</color>").
     */
    public String getFormattedTag() {
        if (tag == null || tag.isEmpty()) return "";
        return "<color:" + getColor() + ">[" + tag + "]</color>";
    }

    /**
     * Obtient le nom formaté en gras avec la couleur hexadécimale de la guilde.
     *
     * @return Le nom stylisé.
     */
    public String getFormattedName() {
        return "<color:" + getColor() + "><bold>" + name + "</bold></color>";
    }

    /**
     * Obtient la description textuelle de la guilde.
     *
     * @return La description.
     */
    public String getDescription() {
        return description;
    }

    /**
     * Modifie la description de la guilde.
     *
     * @param description La nouvelle description.
     */
    public void setDescription(String description) {
        this.description = description != null ? description : "";
    }

    /**
     * Obtient le solde actuel de la banque de guilde.
     *
     * @return Le montant disponible.
     */
    public double getBalance() {
        return balance;
    }

    /**
     * Définit directement le solde bancaire de la guilde.
     *
     * @param balance Le nouveau montant.
     */
    public void setBalance(double balance) {
        this.balance = Math.max(0.0, balance);
    }

    /**
     * Dépose une somme d'argent dans la banque de guilde.
     *
     * @param amount Montant positif à ajouter.
     */
    public void deposit(double amount) {
        if (amount > 0) {
            this.balance += amount;
        }
    }

    /**
     * Tente de retirer une somme d'argent depuis la banque de guilde.
     *
     * @param amount Montant à retirer.
     * @return true si le retrait a réussi, false si le solde était insuffisant.
     */
    public boolean withdraw(double amount) {
        if (amount > 0 && this.balance >= amount) {
            this.balance -= amount;
            return true;
        }
        return false;
    }

    /**
     * Vérifie si la banque de guilde possède au moins un montant spécifié.
     *
     * @param amount Le montant requis.
     * @return true si balance >= amount.
     */
    public boolean hasBalance(double amount) {
        return this.balance >= amount;
    }

    /**
     * Obtient la date et heure de création de la guilde en millisecondes.
     *
     * @return Le timestamp de création.
     */
    public long getCreatedAt() {
        return createdAt;
    }

    // ==========================================
    // GESTION DU HOME DE GUILDE
    // ==========================================

    /**
     * Définit les coordonnées du point de ralliement (home) de la guilde.
     *
     * @param loc L'emplacement Bukkit du home.
     */
    public void setHome(Location loc) {
        if (loc != null && loc.getWorld() != null) {
            this.homeWorld = loc.getWorld().getName();
            this.homeX = loc.getX();
            this.homeY = loc.getY();
            this.homeZ = loc.getZ();
            this.homeYaw = loc.getYaw();
            this.homePitch = loc.getPitch();
        } else {
            this.homeWorld = null;
        }
    }

    /**
     * Récupère l'emplacement Bukkit du home de guilde si configuré et si le monde existe.
     *
     * @return La Location du home ou null si aucun home n'est configuré.
     */
    public Location getHomeLocation() {
        if (homeWorld == null) return null;
        World world = Bukkit.getWorld(homeWorld);
        if (world == null) return null;
        return new Location(world, homeX, homeY, homeZ, homeYaw, homePitch);
    }

    public String getHomeWorld() {
        return homeWorld;
    }

    public double getHomeX() {
        return homeX;
    }

    public double getHomeY() {
        return homeY;
    }

    public double getHomeZ() {
        return homeZ;
    }

    public float getHomeYaw() {
        return homeYaw;
    }

    public float getHomePitch() {
        return homePitch;
    }

    public void setHomeCoordinates(String world, double x, double y, double z, float yaw, float pitch) {
        this.homeWorld = world;
        this.homeX = x;
        this.homeY = y;
        this.homeZ = z;
        this.homeYaw = yaw;
        this.homePitch = pitch;
    }

    // ==========================================
    // GESTION DES MEMBRES
    // ==========================================

    /**
     * Obtient la liste de tous les membres de la guilde.
     *
     * @return Une collection non modifiable des membres.
     */
    public Collection<GuildMember> getMembers() {
        return Collections.unmodifiableCollection(members.values());
    }

    /**
     * Obtient le nombre total de membres dans la guilde.
     *
     * @return Le nombre de membres.
     */
    public int getMemberCount() {
        return members.size();
    }

    /**
     * Récupère un membre spécifique par son UUID.
     *
     * @param playerUuid Le UUID du joueur.
     * @return L'instance GuildMember ou null si le joueur n'est pas membre.
     */
    public GuildMember getMember(UUID playerUuid) {
        if (playerUuid == null) return null;
        return members.get(playerUuid);
    }

    /**
     * Vérifie si un joueur est membre de cette guilde.
     *
     * @param playerUuid Le UUID du joueur.
     * @return true si le joueur est membre, false sinon.
     */
    public boolean isMember(UUID playerUuid) {
        if (playerUuid == null) return false;
        return members.containsKey(playerUuid);
    }

    /**
     * Ajoute un nouveau membre dans la guilde.
     *
     * @param member Le membre à ajouter.
     */
    public void addMember(GuildMember member) {
        if (member != null) {
            members.put(member.getPlayerUuid(), member);
        }
    }

    /**
     * Retire un membre de la guilde.
     *
     * @param playerUuid Le UUID du joueur à retirer.
     * @return Le membre retiré ou null s'il n'était pas présent.
     */
    public GuildMember removeMember(UUID playerUuid) {
        if (playerUuid == null) return null;
        return members.remove(playerUuid);
    }

    // ==========================================
    // GESTION DES RANGS
    // ==========================================

    /**
     * Obtient tous les rangs configurés pour cette guilde.
     *
     * @return Collection non modifiable des rangs.
     */
    public Collection<GuildRank> getRanks() {
        return Collections.unmodifiableCollection(ranks.values());
    }

    /**
     * Récupère un rang par son identifiant unique.
     *
     * @param rankId L'identifiant du rang.
     * @return L'instance GuildRank ou null.
     */
    public GuildRank getRank(String rankId) {
        if (rankId == null) return null;
        return ranks.get(rankId.toLowerCase());
    }

    /**
     * Ajoute ou met à jour un rang dans la guilde.
     *
     * @param rank Le rang à enregistrer.
     */
    public void addRank(GuildRank rank) {
        if (rank != null) {
            ranks.put(rank.getId().toLowerCase(), rank);
        }
    }

    /**
     * Supprime un rang de la guilde.
     *
     * @param rankId L'ID du rang à supprimer.
     * @return Le rang supprimé ou null.
     */
    public GuildRank removeRank(String rankId) {
        if (rankId == null) return null;
        return ranks.remove(rankId.toLowerCase());
    }

    /**
     * Obtient le rang par défaut attribué aux nouvelles recrues.
     *
     * @return Le GuildRank par défaut ou le rang avec la plus faible priorité.
     */
    public GuildRank getDefaultRank() {
        for (GuildRank rank : ranks.values()) {
            if (rank.isDefault()) {
                return rank;
            }
        }
        // Fallback: rang avec la plus faible priorité
        return ranks.values().stream()
                .min(Comparator.comparingInt(GuildRank::getPriority))
                .orElse(null);
    }

    /**
     * Obtient le rang de Chef (la priorité la plus élevée).
     *
     * @return Le GuildRank avec la priorité maximale.
     */
    public GuildRank getLeaderRank() {
        return ranks.values().stream()
                .max(Comparator.comparingInt(GuildRank::getPriority))
                .orElse(null);
    }

    /**
     * Récupère le membre qui occupe le rang de Chef de la guilde.
     *
     * @return Le GuildMember chef ou null.
     */
    public GuildMember getLeaderMember() {
        GuildRank leaderRank = getLeaderRank();
        if (leaderRank == null) return null;
        for (GuildMember member : members.values()) {
            if (member.getRankId().equalsIgnoreCase(leaderRank.getId())) {
                return member;
            }
        }
        return null;
    }

    /**
     * Récupère le rang actuel d'un membre de la guilde.
     *
     * @param playerUuid Le UUID du joueur.
     * @return Son GuildRank ou null s'il n'est pas membre ou n'a pas de rang valide.
     */
    public GuildRank getMemberRank(UUID playerUuid) {
        GuildMember member = getMember(playerUuid);
        if (member == null) return null;
        return getRank(member.getRankId());
    }

    /**
     * Vérifie si un membre de la guilde possède une permission donnée.
     *
     * @param playerUuid Le UUID du joueur.
     * @param permission La permission recherchée.
     * @return true si le joueur est membre et que son rang possède la permission, false sinon.
     */
    public boolean hasPermission(UUID playerUuid, GuildPermission permission) {
        GuildRank rank = getMemberRank(playerUuid);
        if (rank == null) return false;
        return rank.hasPermission(permission);
    }

    // ==========================================
    // GESTION DES CLAIMS (TERRITOIRES)
    // ==========================================

    /**
     * Obtient la liste de tous les territoires (chunks) possédés par la guilde.
     *
     * @return Une liste non modifiable des claims.
     */
    public List<GuildClaim> getClaims() {
        return Collections.unmodifiableList(claims);
    }

    /**
     * Obtient le nombre total de chunks revendiqués par la guilde.
     *
     * @return Le nombre de claims.
     */
    public int getClaimCount() {
        return claims.size();
    }

    /**
     * Ajoute un nouveau chunk revendiqué à la guilde.
     *
     * @param claim Le claim à ajouter.
     */
    public void addClaim(GuildClaim claim) {
        if (claim != null && !claims.contains(claim)) {
            claims.add(claim);
        }
    }

    /**
     * Retire un claim de la guilde selon ses coordonnées.
     *
     * @param world  Nom du monde.
     * @param chunkX Coordonnée X du chunk.
     * @param chunkZ Coordonnée Z du chunk.
     * @return Le GuildClaim supprimé ou null si non trouvé.
     */
    public GuildClaim removeClaim(String world, int chunkX, int chunkZ) {
        for (GuildClaim claim : claims) {
            if (claim.matches(world, chunkX, chunkZ)) {
                claims.remove(claim);
                return claim;
            }
        }
        return null;
    }

    /**
     * Vérifie si un chunk spécifique appartient à cette guilde.
     *
     * @param world  Nom du monde.
     * @param chunkX Coordonnée X du chunk.
     * @param chunkZ Coordonnée Z du chunk.
     * @return true si la guilde possède ce chunk, false sinon.
     */
    public boolean isClaimed(String world, int chunkX, int chunkZ) {
        for (GuildClaim claim : claims) {
            if (claim.matches(world, chunkX, chunkZ)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Calcule le nombre de chunks achetés par la guilde durant une période récente (ex: les dernières 24h).
     *
     * @param sinceMillis Le timestamp à partir duquel compter les claims (ex: now - 24h).
     * @return Le nombre de claims récents.
     */
    public int getRecentClaimsCount(long sinceMillis) {
        int count = 0;
        for (GuildClaim claim : claims) {
            if (claim.getClaimedAt() >= sinceMillis) {
                count++;
            }
        }
        return count;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Guild guild = (Guild) o;
        return Objects.equals(id, guild.id);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id);
    }
}
