package com.slprojects.slcraftplugin;

import com.slprojects.slcraftplugin.commands.admins.WildReset;
import com.slprojects.slcraftplugin.commands.publics.LinkCode;
import com.slprojects.slcraftplugin.commands.publics.Wild;
import com.slprojects.slcraftplugin.parallelTasks.InternalWebServer;
import com.slprojects.slcraftplugin.parallelTasks.events.PeriodicEvent;
import com.slprojects.slcraftplugin.parallelTasks.dataHandlers.PlayerDataHandler;
import com.slprojects.slcraftplugin.utils.ConsoleLog;
import me.clip.placeholderapi.PlaceholderAPI;
import net.luckperms.api.LuckPerms;
import net.luckperms.api.cacheddata.CachedMetaData;
import net.md_5.bungee.api.ChatMessageType;
import net.md_5.bungee.api.chat.TextComponent;
import org.bukkit.Bukkit;
import org.bukkit.Sound;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.AsyncPlayerChatEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.plugin.RegisteredServiceProvider;
import org.bukkit.plugin.java.JavaPlugin;
import org.json.simple.JSONObject;
import org.mariadb.jdbc.MariaDbPoolDataSource;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public final class Main extends JavaPlugin implements Listener {
    // Variables
    private static FileConfiguration config;
    public static LuckPerms luckPermsApi;
    public static String pluginName;

    // Publiques car on les appelle ailleurs
    public PlayerDataHandler playerDataHandler;
    public Wild wildCommand;
    public PeriodicEvent periodicEvent;

    @Override
    public void onEnable() {
        pluginName = this.getName();
        // On s'assure qu'on a placeholder api
        if (getServer().getPluginManager().getPlugin("PlaceholderAPI") != null) {
            ConsoleLog.info("PlaceholderAPI chargé");
            // On initialise les listeners
            getServer().getPluginManager().registerEvents(this, this);
        } else {
            ConsoleLog.danger("PlaceholderAPI n'est pas accessible!");
            getServer().getPluginManager().disablePlugin(this);
        }

        // LuckPerms
        // S'assure que le plugin est installé et évite l'affichage d'une erreur de classe inaccessible
        if (getServer().getPluginManager().getPlugin("LuckPerms") != null) {
            RegisteredServiceProvider<LuckPerms> provider = Bukkit.getServicesManager().getRegistration(LuckPerms.class);
            if (provider != null) {
                ConsoleLog.info("LuckPerms chargé");
                luckPermsApi = provider.getProvider();
            } else {
                ConsoleLog.danger("LuckPerms n'est pas accessible!");
                getServer().getPluginManager().disablePlugin(this);
            }
        } else {
            ConsoleLog.danger("LuckPerms n'est pas accessible!");
            getServer().getPluginManager().disablePlugin(this);
        }

        // Plugin startup logic
        saveDefaultConfig();
        reloadConfig();
        config = getConfig();
        updateConfig();

        initDatabase();

        playerDataHandler = new PlayerDataHandler(this);
        InternalWebServer.startServer(this);
        periodicEvent = new PeriodicEvent(this);

        // On initialise les commandes
        wildCommand = new Wild(this);
        getCommand("wild").setExecutor(wildCommand);

        WildReset wildReset = new WildReset(this);
        getCommand("reset-wild").setExecutor(wildReset);

        LinkCode linkCodeCommand = new LinkCode(this);
        getCommand("getLinkCode").setExecutor(linkCodeCommand);

        ConsoleLog.success("Plugin démarré");
    }

    @Override
    public void onDisable() {
        // Plugin shutdown logic
        ConsoleLog.danger("Plugin désactivé, au revoir!");

        getServer().getOnlinePlayers().forEach(player -> playerDataHandler.quitEvent(player));
    }

    @EventHandler(priority = EventPriority.HIGHEST)
    public void onPlayerJoin(PlayerJoinEvent e) {
        // On désactive le message par défaut
        e.joinMessage(null);
        playerDataHandler.joinEvent(e.getPlayer());

        // On affiche le message de bienvenue
        String welcomeMessage = PlaceholderAPI.setPlaceholders(e.getPlayer(), Objects.requireNonNull(getConfig().getString("player-join-message")));
        // Et on joue un petit son chez tous les joueurs
        for (Player p : getServer().getOnlinePlayers()) {
            p.sendMessage(welcomeMessage);
            if (getConfig().getBoolean("player-join-playSound")) {
                p.playSound(p.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1, 0);
            }
        }
        sendMessageToDiscord("**" + e.getPlayer().getName() + "** vient de rejoindre le serveur");
    }

    @EventHandler(priority = EventPriority.HIGHEST)
    public void onPlayerQuit(PlayerQuitEvent e) {
        // On désactive le message par défaut
        e.quitMessage(null);
        playerDataHandler.quitEvent(e.getPlayer());
        String quitMessage = PlaceholderAPI.setPlaceholders(e.getPlayer(), Objects.requireNonNull(getConfig().getString("player-quit-message")));
        for (Player p : getServer().getOnlinePlayers()) {
            p.sendMessage(quitMessage);
        }
        sendMessageToDiscord("**" + e.getPlayer().getName() + "** a quitté le serveur");
    }

    // On renvoie chaque message des joueurs sur le canal de chat du serveur discord
    @SuppressWarnings({"deprecation"})
    @EventHandler(priority = EventPriority.LOWEST)
    void AsyncChatEvent(AsyncPlayerChatEvent e) {
        String playerFormattedMessage = e.getMessage();
        // On applique les text markup
        // Markdown
        //italique + gras "***"
        playerFormattedMessage = Pattern.compile("\\*\\*\\*(.*?)\\*\\*\\*").matcher(playerFormattedMessage).replaceAll("§l§o$1§r");
        //gras "**"
        playerFormattedMessage = Pattern.compile("\\*\\*(.*?)\\*\\*").matcher(playerFormattedMessage).replaceAll("§l$1§r");
        //italique "*"
        playerFormattedMessage = Pattern.compile("\\*(.*?)\\*").matcher(playerFormattedMessage).replaceAll("§o$1§r");
        //underline
        playerFormattedMessage = Pattern.compile("__(.*?)__").matcher(playerFormattedMessage).replaceAll("§n$1§r");
        //barré
        playerFormattedMessage = Pattern.compile("~~(.*?)~~").matcher(playerFormattedMessage).replaceAll("§m$1§r ");

        // Couleurs
        playerFormattedMessage = Pattern.compile("&([a-f]|r|[0-9])").matcher(playerFormattedMessage).replaceAll("§$1");

        // Ping utilisateur
        Matcher m = Pattern.compile("@(.*?)($|[ ,;:!])").matcher(playerFormattedMessage);
        List<String> playerTags = new ArrayList<>();
        while (m.find()) {
            playerTags.add(m.group(1));
        }
        // On va chercher le préfix dans LuckPerms
        CachedMetaData playerMetaData = luckPermsApi.getPlayerAdapter(Player.class).getMetaData(e.getPlayer());

        for (Player p : Bukkit.getOnlinePlayers()) {
            // Si le joueur a qui on va poster le message (p) a été mentionné
            if (playerTags.contains(p.getName())) {
                // On colorise sa mention
                playerFormattedMessage = Pattern.compile("@(" + p.getName() + ")($|[ ,;:!])").matcher(playerFormattedMessage).replaceAll("§r§l§d@$1§r$2");

                // On lui joue un son + un texte dans la barre d'action
                p.sendMessage(ChatMessageType.ACTION_BAR, TextComponent.fromLegacyText("§b " + e.getPlayer().getName() + " §aVous a mentionné !"));
                p.playSound(p.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 100, 2);
                // On colorie les autres mentions
                playerFormattedMessage = Pattern.compile(" @(.*?)($|[ ,;:!])").matcher(playerFormattedMessage).replaceAll("§r§b @$1§r$2");
            }

            String CompleteMessage = playerMetaData.getPrefix() + e.getPlayer().getName() + "§r: " + playerFormattedMessage;
            // On envoie le message au joueur
            p.sendMessage(CompleteMessage);

            // Et dans la console
            if (e.getPlayer() == p) {
                ConsoleLog.info(CompleteMessage);
            }
        }
        // On envoie le message sur discord (on envoie le msg sans les couleur ni le formatage)
        String discordFriendlyMsg = Pattern.compile("&([a-f]|r|[0-8])").matcher(e.getMessage()).replaceAll("");
        sendMessageToDiscord(discordFriendlyMsg, e.getPlayer().getName());
        // On désactive le message de base de minecraft
        e.setCancelled(true);
    }

    // Permet de faire des appels vers l'api discord
    public String getHttp(String urlString) {
        String returnData = "";
        // Processus long et chiant
        try {
            URL url = new URL(urlString);
            HttpURLConnection con = (HttpURLConnection) url.openConnection();
            con.setRequestMethod("GET");
            con.setRequestProperty("User-Agent", "Mozilla/5.0");
            con.setRequestProperty("Accept-Language", "fr-FR,fr;q=0.5");
            con.setRequestProperty("Content-Type", "application/json");
            con.setRequestProperty("Server-Type", config.getString("server-type"));
            con.setDoOutput(true);
            con.setDoInput(true);
            con.setUseCaches(false);
            con.setAllowUserInteraction(false);
            con.setConnectTimeout(5000);
            con.setReadTimeout(5000);
            con.connect();
            BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
            String inputLine;

            StringBuilder response = new StringBuilder();
            while ((inputLine = in.readLine()) != null) {
                response.append(inputLine);
            }


            in.close();
            con.disconnect();
            returnData = response.toString();
        } catch (Exception ex) {
            ConsoleLog.danger("Impossible de se connecter à l'url " + urlString + ". Func getHttp(String urlString)");
            ex.printStackTrace();
        }

        return returnData;
    }

    // Envoyer un message sur le discord
    @SuppressWarnings({"unchecked"})
    public void sendMessageToDiscord(String message, String username) {
        // On va vérifier que le joueur ne fait pas de @everyone ou de @here
        message = message.replace("<@everyone>", "**everyone**");
        message = message.replace("<@here>", "**here**");
        message = message.replace("@everyone", "**everyone**");
        message = message.replace("@here", "**here**");

        // On forme le JSON
        JSONObject json = new JSONObject();
        json.put("message", message);
        json.put("username", username);

        // On va appeler l'api du bot discord
        try {
            String urlString = config.getString("discordBot-api-url") + "mc/chat/" + URLEncoder.encode(json.toJSONString(), "UTF-8").replace("+", "%20");

            String response = getHttp(urlString);
            if (getConfig().getBoolean("msg-verbose")) {
                ConsoleLog.info("Func AsyncChatEvent(PlayerChatEvent e), HTTP response:" + response);
            }
        } catch (UnsupportedEncodingException ex) {
            ConsoleLog.danger("Impossible de d'encoder les données. Func AsyncChatEvent(PlayerChatEvent e)");
            ex.printStackTrace();
        }
    }

    public void sendMessageToDiscord(String message) {
        sendMessageToDiscord(message, "SL-Craft");
    }

    public Connection bddOpenConn() { // si mot de passe avec des caractère spéciaux
        Connection conn = null;
        try {
            Class.forName("org.mariadb.jdbc.MariaDbPoolDataSource");
        } catch (ClassNotFoundException e) {
            ConsoleLog.danger("Il manque le driver MariaDB!");
            getServer().getPluginManager().disablePlugin(this);
        }
        try {
            MariaDbPoolDataSource dataSource = new MariaDbPoolDataSource("jdbc:mariadb://" + config.getString("database.host") + "/" + config.getString("database.database") + "?user=" + config.getString("database.user") + "&password=" + config.getString("database.password") + "&maxPoolSize=10");
            conn = dataSource.getConnection();
            ConsoleLog.success("Connexion à la base de données réussie!");
        }// ou les saisir
        catch (SQLException e) {
            ConsoleLog.danger("Erreur lors de la connexion à la base de données.");
            getServer().getPluginManager().disablePlugin(this);
        }
        return conn;
    }

    private void updateConfig() {
        ConsoleLog.info("Vérification du fichier de configuration...");
        // 1.6.0
        if (!config.contains("server-type")) {
            ConsoleLog.info("Ajout de la variable serverType dans le fichier de configuration...");
            config.set("server-type", "dev");

            saveConfig();
            reloadConfig();
        }

        if (config.contains("wild") && (config.contains("excluded-biomes") && config.contains("world") && config.contains("max-range"))) {
            ConsoleLog.info("Mise à jour des paramètres concernant la commande /wild");

            config.set("wild.excluded-biomes", config.get("excluded-biomes"));
            config.set("wild.world", config.get("world"));
            config.set("wild.max-range", config.get("max-range"));

            config.set("excluded-biomes", null);
            config.set("world", null);
            config.set("max-range", null);

            config.options().copyDefaults(true);
            saveConfig();
            reloadConfig();
        }

        // 1.6.1 - 1.6.2
        config.options().copyDefaults(true);
        saveConfig();
        reloadConfig();
    }

    private void initDatabase() {
        try {
            Connection con = bddOpenConn();
            PreparedStatement ps = con.prepareStatement("CREATE TABLE IF NOT EXISTS `site_userSetting` (\n" +
                    "  `uuid` varchar(36) NOT NULL DEFAULT '',\n" +
                    "  `name` varchar(128) NOT NULL,\n" +
                    "  `value` text CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,\n" +
                    "  PRIMARY KEY (`uuid`,`name`) USING BTREE\n" +
                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;");
            ps.executeQuery();
            ps = con.prepareStatement("CREATE TABLE IF NOT EXISTS `site_linkCode` (\n" +
                    " `uuid` VARCHAR(36) NOT NULL,\n" +
                    " `code` VARCHAR(8) NOT NULL,\n" +
                    " `time` TIMESTAMP NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),\n" +
                    " `used` BOOLEAN,\n" +
                    " PRIMARY KEY (`uuid`),\n" +
                    " UNIQUE INDEX `code` (`code`)\n" +
                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;");
            ps.executeQuery();
            con.close();
        } catch (Exception e) {
            ConsoleLog.danger("Erreur lors de l'exécution de initDatabase(): " + e);
        }
    }
}