package tc.oc.commons.bukkit.punishment;

import java.time.Duration;
import java.util.List;
import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.inject.Singleton;

import com.google.common.collect.Collections2;
import com.sk89q.minecraft.util.commands.Command;
import com.sk89q.minecraft.util.commands.CommandContext;
import com.sk89q.minecraft.util.commands.CommandException;
import com.sk89q.minecraft.util.commands.CommandPermissionsException;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.TranslatableComponent;
import org.bukkit.command.CommandSender;
import tc.oc.api.docs.Punishment;
import tc.oc.api.docs.User;
import tc.oc.api.model.QueryService;
import tc.oc.api.punishments.PunishmentSearchRequest;
import tc.oc.commons.bukkit.chat.Audiences;
import tc.oc.commons.bukkit.chat.Paginator;
import tc.oc.commons.bukkit.chat.PlayerComponent;
import tc.oc.commons.bukkit.chat.WarningComponent;
import tc.oc.commons.bukkit.commands.CommandUtils;
import tc.oc.commons.bukkit.commands.UserFinder;
import tc.oc.commons.bukkit.nick.IdentityProvider;
import tc.oc.commons.core.chat.Audience;
import tc.oc.commons.core.commands.Commands;
import tc.oc.commons.core.concurrent.Flexecutor;
import tc.oc.minecraft.scheduler.Sync;

import static tc.oc.api.docs.virtual.PunishmentDoc.Type;
import static tc.oc.api.docs.virtual.PunishmentDoc.Type.*;
import static tc.oc.commons.bukkit.commands.UserFinder.Default.NULL;
import static tc.oc.commons.bukkit.commands.UserFinder.Default.SENDER;
import static tc.oc.commons.bukkit.punishment.PunishmentPermissions.fromFlag;
import static tc.oc.commons.bukkit.punishment.PunishmentPermissions.fromType;

@Singleton
public class PunishmentCommands implements Commands {

    private final QueryService<Punishment> punishmentService;
    private final PunishmentCreator punishmentCreator;
    private final PunishmentFormatter punishmentFormatter;
    private final PunishmentEnforcer punishmentEnforcer;
    private final UserFinder userFinder;
    private final IdentityProvider identityProvider;
    private final Audiences audiences;
    private final Flexecutor syncExecutor;

    @Inject PunishmentCommands(QueryService<Punishment> punishmentService,
                               PunishmentCreator punishmentCreator,
                               PunishmentFormatter punishmentFormatter,
                               PunishmentEnforcer punishmentEnforcer,
                               UserFinder userFinder,
                               IdentityProvider identityProvider,
                               Audiences audiences,
                               @Sync Flexecutor syncExecutor) {

        this.punishmentService = punishmentService;
        this.punishmentCreator = punishmentCreator;
        this.punishmentFormatter = punishmentFormatter;
        this.punishmentEnforcer = punishmentEnforcer;
        this.userFinder = userFinder;
        this.identityProvider = identityProvider;
        this.audiences = audiences;
        this.syncExecutor = syncExecutor;
    }

    public boolean flag(char flag, CommandContext args, CommandSender sender) throws CommandException {
        if(args.hasFlag(flag)) {
            if(sender.hasPermission(fromFlag(flag))) {
                return true;
            } throw new CommandPermissionsException();
        }
        return false;
    }

    public boolean permission(CommandSender sender, @Nullable Type type) throws CommandException {
        if(!sender.hasPermission(fromType(type))) {
            throw new CommandPermissionsException();
        }
        return true;
    }

    public void create(CommandContext args, CommandSender sender, @Nullable Type type, @Nullable Duration duration) throws CommandException {
        final User punisher = userFinder.getLocalUser(sender);
        final String reason = args.getRemainingString(duration == null ? 1 : 2);
        final boolean auto = flag('a', args, sender);
        final boolean silent = flag('s', args, sender);
        final boolean offrecord = flag('o', args, sender);
        if(permission(sender, type)) {
            syncExecutor.callback(
                userFinder.findUser(sender, args, 0),
                response -> {
                    punishmentCreator.create(
                        punisher,
                        response.user,
                        reason,
                        type,
                        duration,
                        silent,
                        auto,
                        offrecord
                    );
                }
            );
        }
    }

    @Command(
        aliases = { "w", "warn" },
        flags = "aso",
        usage = "<player> <reason>",
        desc = "Warn a player for a reason.",
        min = 2
    )
    public void warn(CommandContext args, CommandSender sender) throws CommandException {
        create(args, sender, WARN, null);
    }

    @Command(
        aliases = { "k", "kick" },
        flags = "aso",
        usage = "<player> <reason>",
        desc = "Kick a player for a reason.",
        min = 2
    )
    public void kick(CommandContext args, CommandSender sender) throws CommandException {
        create(args, sender, KICK, null);
    }

    @Command(
        aliases = { "tb", "tempban" },
        flags = "aso",
        usage = "<player> <duration> <reason>",
        desc = "Temporarily ban a player for a reason.",
        min = 3
    )
    public void tempban(CommandContext args, CommandSender sender) throws CommandException {
        create(args, sender, BAN, CommandUtils.getDuration(args, 1, Duration.ofDays(7)));
    }

    @Command(
        aliases = { "pb", "permaban" },
        flags = "aso",
        usage = "<player> <reason>",
        desc = "Permanently ban a player for a reason.",
        min = 2
    )
    public void permaban(CommandContext args, CommandSender sender) throws CommandException {
        create(args, sender, BAN, null);
    }

    @Command(
        aliases = { "p", "punish" },
        flags = "aso",
        usage = "<player> <reason>",
        desc = "Punish a player for a reason.",
        min = 2
    )
    public void punish(CommandContext args, CommandSender sender) throws CommandException {
        create(args, sender, null, null); // Website will handle choosing which punishment
    }

    @Command(
        aliases = { "rp", "repeatpunish" },
        usage = "[player]",
        desc = "Show the last punishment you issued or repeat it for a different player",
        min = 0,
        max = 1
    )
    public void repeat(CommandContext args, CommandSender sender) throws CommandException {
        final User punisher = userFinder.getLocalUser(sender);
        final Audience audience = audiences.get(sender);
        if(permission(sender, null)) {
            syncExecutor.callback(
                    userFinder.findUser(sender, args, 0, NULL),
                    punished -> syncExecutor.callback(
                            punishmentService.find(PunishmentSearchRequest.punisher(punisher, 1)),
                            punishments -> punishments.documents().stream().findFirst().<Runnable>map(last -> () -> {
                                if (punished != null) {
                                    punishmentCreator.repeat(last, punished.user);
                                } else {
                                    audience.sendMessages(punishmentFormatter.format(last, false, false));
                                }
                            }).orElse(() -> audience.sendMessage(new WarningComponent("punishment.noneIssued"))).run()
                    )
            );
        }
    }

    @Command(
        aliases = { "l", "lookup" },
        usage = "<player>",
        desc = "Lookup previous punishments for player.",
        min = 0,
        max = 1
    )
    public void lookup(CommandContext args, CommandSender sender) throws CommandException {
        syncExecutor.callback(
            userFinder.findUser(sender, args, 0, SENDER),
            user -> {
                syncExecutor.callback(
                    punishmentService.find(PunishmentSearchRequest.punished(user.user, true, null)),
                    punishments -> {
                        new Paginator<Punishment>() {
                            @Override
                            protected BaseComponent title() {
                                return new TranslatableComponent(
                                    "punishment.lookup",
                                    new PlayerComponent(identityProvider.createIdentity(user))
                                );
                            }
                            @Override
                            protected List<? extends BaseComponent> multiEntry(Punishment entry, int index) {
                                return punishmentFormatter.format(entry, false, false);
                            }
                        }.display(
                            sender,
                            Collections2.filter(punishments.documents(), p -> punishmentEnforcer.viewable(sender, p, false)),
                            args.getInteger(1, 1)
                        );
                    }
                );
            }
        );
    }

}
