package de.simolzimol.mclogger.paper.commands;

import de.simolzimol.mclogger.paper.PaperLoggerPlugin;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextDecoration;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabCompleter;
import org.jetbrains.annotations.NotNull;

import java.sql.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

/**
 * In-game command class for /mclogger.
 *
 * Sub-commands:
 *   /mclogger help                       – Help          (mclogger.use)
 *   /mclogger status                     – DB status     (mclogger.admin)
 *   /mclogger reload                     – Reload config (mclogger.admin)
 *   /mclogger chat     <player> [page]   – Chat log      (mclogger.view.chat)
 *   /mclogger commands <player> [page]   – Command log   (mclogger.view.commands)
 *   /mclogger deaths   <player> [page]   – Death log     (mclogger.view.deaths)
 *   /mclogger sessions <player> [page]   – Session log   (mclogger.view.sessions)
 *   /mclogger blocks   <x> <y> <z> [page]– Block log    (mclogger.view.blocks)
 *   /mclogger perms player <name> [page] – Perms by target player  (mclogger.view.perms)
 *   /mclogger perms actor  <name> [page] – Perms by actor          (mclogger.view.perms)
 *   /mclogger perms group  <name> [page] – Perms by group          (mclogger.view.perms)
 *   /mclogger perms all         [page]   – All perm events         (mclogger.view.perms)
 *
 * @author SimolZimol
 */
public class MCLoggerCommand implements CommandExecutor, TabCompleter {

    private static final int PAGE_SIZE = 8;

    private final PaperLoggerPlugin plugin;

    public MCLoggerCommand(PaperLoggerPlugin plugin) {
        this.plugin = plugin;
    }

    // --------------------------------------------------------
    // Tab-Completion
    // --------------------------------------------------------

    @Override
    public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command cmd,
                                       @NotNull String label, String[] args) {
        if (!sender.hasPermission("mclogger.use")) return List.of();

        if (args.length == 1) {
            List<String> subs = new ArrayList<>(List.of("help", "status", "reload",
                    "chat", "commands", "deaths", "sessions", "blocks", "perms"));
            String partial = args[0].toLowerCase();
            subs.removeIf(s -> !s.startsWith(partial));
            return subs;
        }
        if (args.length == 2 && args[0].equalsIgnoreCase("perms")) {
            return List.of("player", "actor", "group", "all").stream()
                    .filter(s -> s.startsWith(args[1].toLowerCase()))
                    .collect(Collectors.toList());
        }
        if (args.length == 3 && args[0].equalsIgnoreCase("perms")
                && (args[1].equalsIgnoreCase("player") || args[1].equalsIgnoreCase("actor"))) {
            return plugin.getServer().getOnlinePlayers().stream()
                    .map(p -> p.getName())
                    .filter(n -> n.toLowerCase().startsWith(args[2].toLowerCase()))
                    .collect(Collectors.toList());
        }

        if (args.length == 2) {
            String sub = args[0].toLowerCase();
            return switch (sub) {
                case "chat", "commands", "deaths", "sessions", "perms" ->
                        plugin.getServer().getOnlinePlayers().stream()
                              .map(p -> p.getName())
                              .filter(n -> n.toLowerCase().startsWith(args[1].toLowerCase()))
                              .toList();
                default -> List.of();
            };
        }

        return List.of();
    }

    // --------------------------------------------------------
    // Command handler
    // --------------------------------------------------------

    @Override
    public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd,
                              @NotNull String label, String[] args) {
        if (!sender.hasPermission("mclogger.use")) {
            send(sender, NamedTextColor.RED, "You don't have permission to use this command.");
            return true;
        }

        if (args.length == 0) {
            handleHelp(sender);
            return true;
        }

        return switch (args[0].toLowerCase()) {
            case "help"     -> { handleHelp(sender);               yield true; }
            case "status"   -> { handleStatus(sender);             yield true; }
            case "reload"   -> { handleReload(sender);             yield true; }
            case "chat"     -> { handleChat(sender, args);         yield true; }
            case "commands" -> { handleCommands(sender, args);     yield true; }
            case "deaths"   -> { handleDeaths(sender, args);       yield true; }
            case "sessions" -> { handleSessions(sender, args);     yield true; }
            case "blocks"   -> { handleBlocks(sender, args);       yield true; }
            case "perms"    -> { handlePerms(sender, args);        yield true; }
            default         -> { handleHelp(sender);               yield true; }
        };
    }

    // --------------------------------------------------------
    // Sub-commands
    // --------------------------------------------------------

    private void handleHelp(CommandSender s) {
        sendHeader(s, "MCLogger Help");
        sendLine(s, "/mcl help",                         "This help");
        if (s.hasPermission("mclogger.admin")) {
            sendLine(s, "/mcl status",                   "Database status");
            sendLine(s, "/mcl reload",                   "Reload configuration");
        }
        if (s.hasPermission("mclogger.view.chat"))
            sendLine(s, "/mcl chat <player> [page]",     "Chat history");
        if (s.hasPermission("mclogger.view.commands"))
            sendLine(s, "/mcl commands <player> [page]", "Command history");
        if (s.hasPermission("mclogger.view.deaths"))
            sendLine(s, "/mcl deaths <player> [page]",   "Death history");
        if (s.hasPermission("mclogger.view.sessions"))
            sendLine(s, "/mcl sessions <player> [page]", "Session history");
        if (s.hasPermission("mclogger.view.blocks"))
            sendLine(s, "/mcl blocks <x> <y> <z> [page]","Block log at position");
        if (s.hasPermission("mclogger.view.perms"))
            sendLine(s, "/mcl perms <player> [page]",    "LuckPerms changes");
    }

    private void handleStatus(CommandSender s) {
        requirePerm(s, "mclogger.admin", () -> {
            boolean ok = plugin.getDb().isConnected();
            send(s, ok ? NamedTextColor.GREEN : NamedTextColor.RED,
                    "Database connection: " + (ok ? "✔ CONNECTED" : "✘ DISCONNECTED"));
            send(s, NamedTextColor.AQUA, "Server: " + plugin.getDb().getServerName()
                    + "  |  ID: " + plugin.getDb().getServerId());
        });
    }

    private void handleReload(CommandSender s) {
        requirePerm(s, "mclogger.admin", () -> {
            plugin.reloadConfig();
            send(s, NamedTextColor.GREEN, "Configuration reloaded.");
        });
    }

    // --  Chat  -----------------------------------------------

    private void handleChat(CommandSender s, String[] args) {
        requirePerm(s, "mclogger.view.chat", () -> {
            if (args.length < 2) { send(s, NamedTextColor.YELLOW, "Usage: /mcl chat <player> [page]"); return; }  
            String player = args[1];
            int page = parsePage(args, 2);
            runQuery(s,
                "SELECT timestamp, message FROM player_chat " +
                "WHERE player_name = ? ORDER BY timestamp DESC LIMIT ? OFFSET ?",
                player, page,
                rs -> {
                    sendHeader(s, "Chat: " + player + " (page " + page + ")");
                    while (rs.next()) {
                        send(s, NamedTextColor.GRAY, "[" + fmtTs(rs.getString("timestamp")) + "] "
                                + rs.getString("message"));
                    }
                });
        });
    }

    // --  Commands  -------------------------------------------

    private void handleCommands(CommandSender s, String[] args) {
        requirePerm(s, "mclogger.view.commands", () -> {
            if (args.length < 2) { send(s, NamedTextColor.YELLOW, "Usage: /mcl commands <player> [page]"); return; }
            String player = args[1];
            int page = parsePage(args, 2);
            runQuery(s,
                "SELECT timestamp, command FROM player_commands " +
                "WHERE player_name = ? ORDER BY timestamp DESC LIMIT ? OFFSET ?",
                player, page,
                rs -> {
                    sendHeader(s, "Commands: " + player + " (page " + page + ")");
                    while (rs.next()) {
                        send(s, NamedTextColor.GRAY, "[" + fmtTs(rs.getString("timestamp")) + "] "
                                + rs.getString("command"));
                    }
                });
        });
    }

    // --  Deaths  ---------------------------------------------

    private void handleDeaths(CommandSender s, String[] args) {
        requirePerm(s, "mclogger.view.deaths", () -> {
            if (args.length < 2) { send(s, NamedTextColor.YELLOW, "Usage: /mcl deaths <player> [page]"); return; }
            String player = args[1];
            int page = parsePage(args, 2);
            runQuery(s,
                "SELECT timestamp, death_message, world, x, y, z FROM player_deaths " +
                "WHERE player_name = ? ORDER BY timestamp DESC LIMIT ? OFFSET ?",
                player, page,
                rs -> {
                    sendHeader(s, "Deaths: " + player + " (page " + page + ")");
                    while (rs.next()) {
                        send(s, NamedTextColor.RED,
                                "[" + fmtTs(rs.getString("timestamp")) + "] " + rs.getString("death_message")
                                + "  @ " + rs.getString("world") + " "
                                + (int)rs.getDouble("x") + "/"
                                + (int)rs.getDouble("y") + "/"
                                + (int)rs.getDouble("z"));
                    }
                });
        });
    }

    // --  Sessions  -------------------------------------------

    private void handleSessions(CommandSender s, String[] args) {
        requirePerm(s, "mclogger.view.sessions", () -> {
            if (args.length < 2) { send(s, NamedTextColor.YELLOW, "Usage: /mcl sessions <player> [page]"); return; }
            String player = args[1];
            int page = parsePage(args, 2);
            runQuery(s,
                "SELECT login_time, logout_time, duration_sec, ip_address, country, server_name FROM player_sessions " +
                "WHERE player_name = ? ORDER BY login_time DESC LIMIT ? OFFSET ?",
                player, page,
                rs -> {
                    sendHeader(s, "Sessions: " + player + " (page " + page + ")");
                    while (rs.next()) {
                        long dur = rs.getLong("duration_sec");
                        String durStr = dur > 0 ? formatDuration(dur) : "ongoing";
                        String country = rs.getString("country");
                        String countryStr = (country != null && !country.isEmpty()) ? "  [" + country + "]" : "";
                        send(s, NamedTextColor.AQUA,
                                "[" + fmtTs(rs.getString("login_time")) + "] "
                                + rs.getString("server_name") + "  " + durStr
                                + "  IP: " + rs.getString("ip_address") + countryStr);
                    }
                });
        });
    }

    // --  Blocks  ---------------------------------------------

    private void handleBlocks(CommandSender s, String[] args) {
        requirePerm(s, "mclogger.view.blocks", () -> {
            if (args.length < 4) { send(s, NamedTextColor.YELLOW, "Usage: /mcl blocks <x> <y> <z> [page]"); return; }
            int bx, by, bz;
            try { bx = Integer.parseInt(args[1]); by = Integer.parseInt(args[2]); bz = Integer.parseInt(args[3]); }
            catch (NumberFormatException ex) { send(s, NamedTextColor.RED, "Coordinates must be integers."); return; }
            int page = parsePage(args, 4);
            runQuery(s,
                "SELECT timestamp, player_name, event_type, block_type FROM block_events " +
                "WHERE x = ? AND y = ? AND z = ? ORDER BY timestamp DESC LIMIT ? OFFSET ?",
                bx, by, bz, page,
                rs -> {
                    sendHeader(s, "Blocks @ " + bx + "/" + by + "/" + bz + " (page " + page + ")");
                    while (rs.next()) {
                        send(s, NamedTextColor.GOLD,
                                "[" + fmtTs(rs.getString("timestamp")) + "] "
                                + rs.getString("player_name") + " "
                                + rs.getString("event_type") + " "
                                + rs.getString("block_type"));
                    }
                });
        });
    }

    // --  Perms  ----------------------------------------------

    private void handlePerms(CommandSender s, String[] args) {
        requirePerm(s, "mclogger.view.perms", () -> {
            if (args.length < 2) {
                send(s, NamedTextColor.YELLOW,
                    "Usage: /mcl perms <player|actor|group|all> [name] [page]");
                return;
            }
            String type = args[1].toLowerCase();
            switch (type) {
                case "all" -> {
                    int page = parsePage(args, 2);
                    runQueryParams(s,
                        "SELECT timestamp, event_type, player_name, actor_name, target_type, action, server_name FROM plugin_events " +
                        "WHERE plugin_name = 'LuckPerms' ORDER BY timestamp DESC LIMIT ? OFFSET ?",
                        List.of(), page,
                        rs -> {
                            sendHeader(s, "All Permission Events (page " + page + ")");
                            while (rs.next()) printPermRow(s, rs);
                        });
                }
                case "player" -> {
                    if (args.length < 3) { send(s, NamedTextColor.YELLOW, "Usage: /mcl perms player <name> [page]"); return; }
                    String name = args[2]; int page = parsePage(args, 3);
                    runQueryParams(s,
                        "SELECT timestamp, event_type, player_name, actor_name, target_type, action, server_name FROM plugin_events " +
                        "WHERE plugin_name = 'LuckPerms' AND (player_name = ? OR player_name LIKE ?) ORDER BY timestamp DESC LIMIT ? OFFSET ?",
                        List.of(name, name + "@%"), page,
                        rs -> {
                            sendHeader(s, "Perms for player " + name + " (page " + page + ")");
                            while (rs.next()) printPermRow(s, rs);
                        });
                }
                case "actor" -> {
                    if (args.length < 3) { send(s, NamedTextColor.YELLOW, "Usage: /mcl perms actor <name> [page]"); return; }
                    String name = args[2]; int page = parsePage(args, 3);
                    runQueryParams(s,
                        "SELECT timestamp, event_type, player_name, actor_name, target_type, action, server_name FROM plugin_events " +
                        "WHERE plugin_name = 'LuckPerms' AND (actor_name = ? OR actor_name LIKE ?) ORDER BY timestamp DESC LIMIT ? OFFSET ?",
                        List.of(name, name + "@%"), page,
                        rs -> {
                            sendHeader(s, "Perms by actor " + name + " (page " + page + ")");
                            while (rs.next()) printPermRow(s, rs);
                        });
                }
                case "group" -> {
                    if (args.length < 3) { send(s, NamedTextColor.YELLOW, "Usage: /mcl perms group <name> [page]"); return; }
                    String name = args[2]; int page = parsePage(args, 3);
                    runQueryParams(s,
                        "SELECT timestamp, event_type, player_name, actor_name, target_type, action, server_name FROM plugin_events " +
                        "WHERE plugin_name = 'LuckPerms' AND target_type = 'group' AND target_id = ? ORDER BY timestamp DESC LIMIT ? OFFSET ?",
                        List.of(name), page,
                        rs -> {
                            sendHeader(s, "Perms for group " + name + " (page " + page + ")");
                            while (rs.next()) printPermRow(s, rs);
                        });
                }
                default -> send(s, NamedTextColor.YELLOW,
                    "Usage: /mcl perms <player|actor|group|all> [name] [page]");
            }
        });
    }

    private void printPermRow(CommandSender s, ResultSet rs) throws SQLException {
        String srv = rs.getString("server_name");
        String srvStr = (srv != null && !srv.isEmpty()) ? " [" + srv + "]" : "";
        send(s, NamedTextColor.LIGHT_PURPLE,
            "[" + fmtTs(rs.getString("timestamp")) + "]" + srvStr + " "
            + rs.getString("actor_name") + " → " + rs.getString("action")
            + (rs.getString("player_name") != null ? " (player: " + rs.getString("player_name") + ")" : ""));
    }

    // --------------------------------------------------------
    // DB helper infrastructure (sync – acceptable for short admin queries)
    // --------------------------------------------------------

    @FunctionalInterface
    interface RsConsumer { void accept(ResultSet rs) throws SQLException; }

    /** Executes a paginated query asynchronously (acceptable for admin commands). */
    private void runQuery(CommandSender s, String sql, String player, int page, RsConsumer consumer) {
        int offset = (page - 1) * PAGE_SIZE;
        plugin.getServer().getScheduler().runTaskAsynchronously(plugin, () -> {
            try (Connection con = plugin.getDb().getConnection();
                 PreparedStatement ps = con.prepareStatement(sql)) {
                ps.setString(1, player);
                ps.setInt(2, PAGE_SIZE);
                ps.setInt(3, offset);
                ResultSet rs = ps.executeQuery();
                plugin.getServer().getScheduler().runTask(plugin, () -> {
                    try { consumer.accept(rs); }
                    catch (SQLException ex) { send(s, NamedTextColor.RED, "Database error: " + ex.getMessage()); }
                });
            } catch (SQLException ex) {
                send(s, NamedTextColor.RED, "Database error: " + ex.getMessage());
            }
        });
    }

    /** Overload for queries with a variable list of leading params before LIMIT/OFFSET. */
    private void runQueryParams(CommandSender s, String sql, List<String> params, int page, RsConsumer consumer) {
        int offset = (page - 1) * PAGE_SIZE;
        plugin.getServer().getScheduler().runTaskAsynchronously(plugin, () -> {
            try (Connection con = plugin.getDb().getConnection();
                 PreparedStatement ps = con.prepareStatement(sql)) {
                int i = 1;
                for (String p : params) ps.setString(i++, p);
                ps.setInt(i++, PAGE_SIZE);
                ps.setInt(i, offset);
                ResultSet rs = ps.executeQuery();
                plugin.getServer().getScheduler().runTask(plugin, () -> {
                    try { consumer.accept(rs); }
                    catch (SQLException ex) { send(s, NamedTextColor.RED, "Database error: " + ex.getMessage()); }
                });
            } catch (SQLException ex) {
                send(s, NamedTextColor.RED, "Database error: " + ex.getMessage());
            }
        });
    }

    /** Overload for block queries with 3 coordinates. */
    private void runQuery(CommandSender s, String sql,
                           int bx, int by, int bz, int page, RsConsumer consumer) {
        int offset = (page - 1) * PAGE_SIZE;
        plugin.getServer().getScheduler().runTaskAsynchronously(plugin, () -> {
            try (Connection con = plugin.getDb().getConnection();
                 PreparedStatement ps = con.prepareStatement(sql)) {
                ps.setInt(1, bx);
                ps.setInt(2, by);
                ps.setInt(3, bz);
                ps.setInt(4, PAGE_SIZE);
                ps.setInt(5, offset);
                ResultSet rs = ps.executeQuery();
                plugin.getServer().getScheduler().runTask(plugin, () -> {
                    try { consumer.accept(rs); }
                    catch (SQLException ex) { send(s, NamedTextColor.RED, "Database error: " + ex.getMessage()); }
                });
            } catch (SQLException ex) {
                send(s, NamedTextColor.RED, "Database error: " + ex.getMessage());
            }
        });
    }

    // --------------------------------------------------------
    // Helper methods
    // --------------------------------------------------------

    private void requirePerm(CommandSender s, String perm, Runnable action) {
        if (!s.hasPermission(perm)) {
            send(s, NamedTextColor.RED, "No permission: " + perm);
        } else {
            action.run();
        }
    }

    private int parsePage(String[] args, int idx) {
        if (args.length > idx) {
            try { return Math.max(1, Integer.parseInt(args[idx])); }
            catch (NumberFormatException ignored) {}
        }
        return 1;
    }

    private static final DateTimeFormatter EU_TS = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm:ss");
    private static final DateTimeFormatter DB_TS = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    private String fmtTs(String raw) {
        if (raw == null) return "-";
        try {
            String s = raw.contains(".") ? raw.substring(0, raw.indexOf('.')) : raw;
            return LocalDateTime.parse(s, DB_TS).format(EU_TS);
        } catch (Exception e) {
            return raw;
        }
    }

    private String formatDuration(long seconds) {
        long h = seconds / 3600, m = (seconds % 3600) / 60, sec = seconds % 60;
        return String.format("%02d:%02d:%02d", h, m, sec);
    }

    private void sendHeader(CommandSender s, String title) {
        s.sendMessage(Component.text("══ " + title + " ══")
                .color(NamedTextColor.GOLD)
                .decorate(TextDecoration.BOLD));
    }

    private void sendLine(CommandSender s, String cmd, String desc) {
        s.sendMessage(Component.text("  " + cmd + " ")
                .color(NamedTextColor.YELLOW)
                .append(Component.text("– " + desc).color(NamedTextColor.GRAY)));
    }

    private void send(CommandSender s, NamedTextColor color, String text) {
        s.sendMessage(Component.text(text).color(color));
    }
}
