package com.tiedup.remake.commands;

import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.IntegerArgumentType;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.state.SocialData;
import com.tiedup.remake.util.MessageDispatcher;
import java.util.*;
import java.util.Optional;
import net.minecraft.ChatFormatting;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.minecraft.commands.arguments.EntityArgument;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.phys.AABB;

/**
 * Social and RP commands.
 *
 * Commands:
 *   /blockplayer <player> - Block a player from interacting with you
 *   /unblockplayer <player> - Unblock a player
 *   /checkblocked <player> - Check if a player has blocked you
 *   /norp - Announce non-consent to current RP (cooldown 45s)
 *   /me <action> - Roleplay action message in local area
 *   /pm <player> <message> - Private message to a player
 *   /talkarea [distance] - Set local chat area distance
 *
 * These commands are usable while tied up.
 *
 * Data persistence: Block lists and talk area settings are stored in SocialData
 * (SavedData) and persist across server restarts.
 */
public class SocialCommand {

    // Cooldowns for /norp (UUID -> last use timestamp)
    // Note: Cooldowns are intentionally NOT persisted - they reset on server restart
    private static final Map<UUID, Long> NORP_COOLDOWNS = new HashMap<>();
    private static final long NORP_COOLDOWN_MS = 45000; // 45 seconds

    /** Remove player cooldown on disconnect to prevent memory leak. */
    public static void cleanupPlayer(UUID playerId) {
        NORP_COOLDOWNS.remove(playerId);
    }

    public static void register(
        CommandDispatcher<CommandSourceStack> dispatcher
    ) {
        dispatcher.register(createBlockPlayerCommand());
        dispatcher.register(createUnblockPlayerCommand());
        dispatcher.register(createCheckBlockedCommand());
        dispatcher.register(createNoRPCommand());
        dispatcher.register(createMeCommand());
        dispatcher.register(createPMCommand());
        dispatcher.register(createTalkAreaCommand());
        dispatcher.register(createTalkInfoCommand());
    }

    // === Command Builders (for use as subcommands of /tiedup) ===

    public static com.mojang.brigadier.builder.LiteralArgumentBuilder<
        CommandSourceStack
    > createBlockPlayerCommand() {
        return Commands.literal("blockplayer").then(
            Commands.argument("player", EntityArgument.player()).executes(
                SocialCommand::blockPlayer
            )
        );
    }

    public static com.mojang.brigadier.builder.LiteralArgumentBuilder<
        CommandSourceStack
    > createUnblockPlayerCommand() {
        return Commands.literal("unblockplayer").then(
            Commands.argument("player", EntityArgument.player()).executes(
                SocialCommand::unblockPlayer
            )
        );
    }

    public static com.mojang.brigadier.builder.LiteralArgumentBuilder<
        CommandSourceStack
    > createCheckBlockedCommand() {
        return Commands.literal("checkblocked").then(
            Commands.argument("player", EntityArgument.player()).executes(
                SocialCommand::checkBlocked
            )
        );
    }

    public static com.mojang.brigadier.builder.LiteralArgumentBuilder<
        CommandSourceStack
    > createNoRPCommand() {
        return Commands.literal("norp").executes(SocialCommand::noRP);
    }

    public static com.mojang.brigadier.builder.LiteralArgumentBuilder<
        CommandSourceStack
    > createMeCommand() {
        return Commands.literal("me").then(
            Commands.argument(
                "action",
                StringArgumentType.greedyString()
            ).executes(SocialCommand::meAction)
        );
    }

    public static com.mojang.brigadier.builder.LiteralArgumentBuilder<
        CommandSourceStack
    > createPMCommand() {
        return Commands.literal("pm").then(
            Commands.argument("player", EntityArgument.player()).then(
                Commands.argument(
                    "message",
                    StringArgumentType.greedyString()
                ).executes(SocialCommand::privateMessage)
            )
        );
    }

    public static com.mojang.brigadier.builder.LiteralArgumentBuilder<
        CommandSourceStack
    > createTalkAreaCommand() {
        return Commands.literal("talkarea")
            .executes(ctx -> setTalkArea(ctx, 0)) // Disable
            .then(
                Commands.argument(
                    "distance",
                    IntegerArgumentType.integer(1, 100)
                ).executes(ctx ->
                    setTalkArea(
                        ctx,
                        IntegerArgumentType.getInteger(ctx, "distance")
                    )
                )
            );
    }

    public static com.mojang.brigadier.builder.LiteralArgumentBuilder<
        CommandSourceStack
    > createTalkInfoCommand() {
        return Commands.literal("talkinfo").executes(SocialCommand::talkInfo);
    }

    // Block System

    private static int blockPlayer(CommandContext<CommandSourceStack> context)
        throws CommandSyntaxException {
        CommandSourceStack source = context.getSource();

        Optional<ServerPlayer> playerOpt = CommandHelper.getPlayerOrFail(
            source
        );
        if (playerOpt.isEmpty()) return 0;
        ServerPlayer player = playerOpt.get();

        ServerPlayer target = EntityArgument.getPlayer(context, "player");

        if (player.getUUID().equals(target.getUUID())) {
            source.sendFailure(Component.translatable("command.tiedup.social.cannot_block_self"));
            return 0;
        }

        SocialData data = SocialData.get(player.serverLevel());

        if (data.isBlocked(player.getUUID(), target.getUUID())) {
            source.sendFailure(
                Component.translatable("command.tiedup.social.already_blocked", target.getName().getString())
            );
            return 0;
        }

        data.addBlock(player.getUUID(), target.getUUID());
        source.sendSuccess(
            () ->
                Component.translatable("command.tiedup.social.blocked", target.getName().getString()).withStyle(ChatFormatting.GREEN),
            false
        );

        TiedUpMod.LOGGER.info(
            "[SOCIAL] {} blocked {}",
            player.getName().getString(),
            target.getName().getString()
        );
        return 1;
    }

    private static int unblockPlayer(CommandContext<CommandSourceStack> context)
        throws CommandSyntaxException {
        CommandSourceStack source = context.getSource();

        Optional<ServerPlayer> playerOpt = CommandHelper.getPlayerOrFail(
            source
        );
        if (playerOpt.isEmpty()) return 0;
        ServerPlayer player = playerOpt.get();

        ServerPlayer target = EntityArgument.getPlayer(context, "player");

        SocialData data = SocialData.get(player.serverLevel());

        if (!data.isBlocked(player.getUUID(), target.getUUID())) {
            source.sendFailure(
                Component.translatable("command.tiedup.social.not_blocked", target.getName().getString())
            );
            return 0;
        }

        data.removeBlock(player.getUUID(), target.getUUID());
        source.sendSuccess(
            () ->
                Component.translatable("command.tiedup.social.unblocked", target.getName().getString()).withStyle(ChatFormatting.GREEN),
            false
        );

        return 1;
    }

    private static int checkBlocked(CommandContext<CommandSourceStack> context)
        throws CommandSyntaxException {
        CommandSourceStack source = context.getSource();

        Optional<ServerPlayer> playerOpt = CommandHelper.getPlayerOrFail(
            source
        );
        if (playerOpt.isEmpty()) return 0;
        ServerPlayer player = playerOpt.get();

        ServerPlayer target = EntityArgument.getPlayer(context, "player");

        SocialData data = SocialData.get(player.serverLevel());
        boolean blocked = data.isBlocked(target.getUUID(), player.getUUID());

        if (blocked) {
            source.sendSuccess(
                () ->
                    Component.translatable("command.tiedup.social.has_blocked_you", target.getName().getString()).withStyle(ChatFormatting.RED),
                false
            );
        } else {
            source.sendSuccess(
                () ->
                    Component.translatable("command.tiedup.social.has_not_blocked_you", target.getName().getString()).withStyle(ChatFormatting.GREEN),
                false
            );
        }

        return 1;
    }

    /**
     * Check if a player is blocked by another.
     * Can be used by other systems to check interaction permissions.
     *
     * @param level The server level to get SocialData from
     * @param blocker The player who may have blocked
     * @param blocked The player who may be blocked
     * @return true if blocker has blocked blocked
     */
    public static boolean isBlocked(
        ServerLevel level,
        UUID blocker,
        UUID blocked
    ) {
        return SocialData.get(level).isBlocked(blocker, blocked);
    }

    // RP Commands

    private static int noRP(CommandContext<CommandSourceStack> context)
        throws CommandSyntaxException {
        CommandSourceStack source = context.getSource();

        Optional<ServerPlayer> playerOpt = CommandHelper.getPlayerOrFail(
            source
        );
        if (playerOpt.isEmpty()) return 0;
        ServerPlayer player = playerOpt.get();

        // Check cooldown
        long now = System.currentTimeMillis();
        Long lastUse = NORP_COOLDOWNS.get(player.getUUID());
        if (lastUse != null && now - lastUse < NORP_COOLDOWN_MS) {
            long remaining = (NORP_COOLDOWN_MS - (now - lastUse)) / 1000;
            source.sendFailure(
                Component.translatable("command.tiedup.social.norp_cooldown", remaining)
            );
            return 0;
        }

        // Set cooldown
        NORP_COOLDOWNS.put(player.getUUID(), now);

        // Broadcast to all players
        Component message = Component.literal("")
            .append(
                Component.translatable("command.tiedup.social.norp_prefix").withStyle(
                    ChatFormatting.RED,
                    ChatFormatting.BOLD
                )
            )
            .append(
                Component.literal(player.getName().getString()).withStyle(
                    ChatFormatting.YELLOW
                )
            )
            .append(
                Component.translatable(
                    "command.tiedup.social.norp_announcement"
                ).withStyle(ChatFormatting.RED)
            );

        // Broadcast to all players (earplug-aware)
        for (ServerPlayer p : player.server.getPlayerList().getPlayers()) {
            MessageDispatcher.sendTo(p, message);
        }

        TiedUpMod.LOGGER.info(
            "[SOCIAL] {} used /norp",
            player.getName().getString()
        );
        return 1;
    }

    private static int meAction(CommandContext<CommandSourceStack> context)
        throws CommandSyntaxException {
        CommandSourceStack source = context.getSource();

        Optional<ServerPlayer> playerOpt = CommandHelper.getPlayerOrFail(
            source
        );
        if (playerOpt.isEmpty()) return 0;
        ServerPlayer player = playerOpt.get();

        String action = StringArgumentType.getString(context, "action");

        // Get talk area for local chat
        SocialData data = SocialData.get(player.serverLevel());
        int talkArea = data.getTalkArea(player.getUUID());

        Component message = Component.literal("")
            .append(
                Component.literal("* ").withStyle(ChatFormatting.LIGHT_PURPLE)
            )
            .append(
                Component.literal(player.getName().getString()).withStyle(
                    ChatFormatting.LIGHT_PURPLE
                )
            )
            .append(
                Component.literal(" " + action).withStyle(
                    ChatFormatting.LIGHT_PURPLE
                )
            );

        if (talkArea > 0) {
            // Local chat - send to nearby players (earplug-aware)
            AABB area = player.getBoundingBox().inflate(talkArea);
            List<ServerPlayer> nearby = player
                .serverLevel()
                .getEntitiesOfClass(ServerPlayer.class, area);
            for (ServerPlayer p : nearby) {
                MessageDispatcher.sendTo(p, message);
            }
        } else {
            // Global chat (earplug-aware)
            for (ServerPlayer p : player.server.getPlayerList().getPlayers()) {
                MessageDispatcher.sendTo(p, message);
            }
        }

        return 1;
    }

    private static int privateMessage(
        CommandContext<CommandSourceStack> context
    ) throws CommandSyntaxException {
        CommandSourceStack source = context.getSource();

        Optional<ServerPlayer> senderOpt = CommandHelper.getPlayerOrFail(
            source
        );
        if (senderOpt.isEmpty()) return 0;
        ServerPlayer sender = senderOpt.get();

        ServerPlayer target = EntityArgument.getPlayer(context, "player");
        String message = StringArgumentType.getString(context, "message");

        // Check if blocked
        SocialData data = SocialData.get(sender.serverLevel());
        if (data.isBlocked(target.getUUID(), sender.getUUID())) {
            source.sendFailure(
                Component.translatable("command.tiedup.social.pm_blocked")
            );
            return 0;
        }

        // Send to target (earplug-aware)
        Component toTarget = Component.literal("")
            .append(
                Component.translatable(
                    "command.tiedup.social.pm_from", sender.getName().getString()
                ).withStyle(ChatFormatting.LIGHT_PURPLE)
            )
            .append(Component.literal(message).withStyle(ChatFormatting.WHITE));
        MessageDispatcher.sendFrom(sender, target, toTarget);

        // Confirm to sender (always show - they're the one sending)
        Component toSender = Component.literal("")
            .append(
                Component.translatable(
                    "command.tiedup.social.pm_to", target.getName().getString()
                ).withStyle(ChatFormatting.GRAY)
            )
            .append(Component.literal(message).withStyle(ChatFormatting.WHITE));
        MessageDispatcher.sendSystemMessage(sender, toSender);

        return 1;
    }

    // Talk Area

    private static int setTalkArea(
        CommandContext<CommandSourceStack> context,
        int distance
    ) throws CommandSyntaxException {
        CommandSourceStack source = context.getSource();

        Optional<ServerPlayer> playerOpt = CommandHelper.getPlayerOrFail(
            source
        );
        if (playerOpt.isEmpty()) return 0;
        ServerPlayer player = playerOpt.get();

        SocialData data = SocialData.get(player.serverLevel());
        data.setTalkArea(player.getUUID(), distance);

        if (distance == 0) {
            source.sendSuccess(
                () -> Component.translatable("command.tiedup.social.talkarea_disabled").withStyle(ChatFormatting.GREEN),
                false
            );
        } else {
            source.sendSuccess(
                () ->
                    Component.translatable("command.tiedup.social.talkarea_set", distance).withStyle(ChatFormatting.GREEN),
                false
            );
        }

        return 1;
    }

    private static int talkInfo(CommandContext<CommandSourceStack> context)
        throws CommandSyntaxException {
        CommandSourceStack source = context.getSource();

        Optional<ServerPlayer> playerOpt = CommandHelper.getPlayerOrFail(
            source
        );
        if (playerOpt.isEmpty()) return 0;
        ServerPlayer player = playerOpt.get();

        SocialData data = SocialData.get(player.serverLevel());
        int talkArea = data.getTalkArea(player.getUUID());

        if (talkArea == 0) {
            source.sendSuccess(
                () ->
                    Component.translatable("command.tiedup.social.talkinfo_disabled").withStyle(ChatFormatting.YELLOW),
                false
            );
        } else {
            source.sendSuccess(
                () -> Component.translatable("command.tiedup.social.talkinfo_distance", talkArea).withStyle(ChatFormatting.YELLOW),
                false
            );
        }

        return 1;
    }
}
