package tc.oc.commons.bukkit.punishment;

import javax.inject.Inject;
import javax.inject.Singleton;

import org.bukkit.Sound;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import tc.oc.api.bukkit.users.OnlinePlayers;
import tc.oc.api.docs.Punishment;
import tc.oc.api.docs.Server;
import tc.oc.api.docs.virtual.PunishmentDoc;
import tc.oc.api.docs.virtual.ServerDoc;
import tc.oc.api.message.MessageListener;
import tc.oc.api.message.MessageQueue;
import tc.oc.api.message.types.ModelUpdate;
import tc.oc.api.model.UpdateService;
import tc.oc.api.util.Permissions;
import tc.oc.commons.bukkit.chat.Audiences;
import tc.oc.commons.bukkit.chat.BukkitSound;
import tc.oc.commons.bukkit.nick.IdentityProvider;
import tc.oc.commons.bukkit.settings.SettingManagerProvider;
import tc.oc.commons.bukkit.teleport.PlayerServerChanger;
import tc.oc.commons.core.chat.Audience;
import tc.oc.commons.core.concurrent.Flexecutor;
import tc.oc.minecraft.api.event.Enableable;
import tc.oc.minecraft.scheduler.Sync;

import static tc.oc.commons.bukkit.punishment.PunishmentMessageSetting.Options;
import static tc.oc.commons.bukkit.punishment.PunishmentPermissions.LOOK_UP;
import static tc.oc.commons.bukkit.punishment.PunishmentPermissions.LOOK_UP_STALE;

@Singleton
public class PunishmentEnforcer implements Enableable, MessageListener {

    private final PunishmentFormatter punishmentFormatter;
    private final UpdateService<PunishmentDoc.Partial> punishmentService;
    private final MessageQueue queue;
    private final Flexecutor executor;
    private final Server localServer;
    private final OnlinePlayers players;
    private final IdentityProvider identities;
    private final Audiences audiences;
    private final PlayerServerChanger playerServerChanger;
    private final SettingManagerProvider settings;

    @Inject PunishmentEnforcer(PunishmentFormatter punishmentFormatter,
                               UpdateService<PunishmentDoc.Partial> punishmentService,
                               MessageQueue queue,
                               @Sync Flexecutor executor,
                               Server localServer,
                               OnlinePlayers players,
                               IdentityProvider identities,
                               Audiences audiences,
                               PlayerServerChanger playerServerChanger,
                               SettingManagerProvider settings) {

        this.punishmentFormatter = punishmentFormatter;
        this.punishmentService = punishmentService;
        this.queue = queue;
        this.executor = executor;
        this.localServer = localServer;
        this.players = players;
        this.identities = identities;
        this.audiences = audiences;
        this.playerServerChanger = playerServerChanger;
        this.settings = settings;
    }

    @Override
    public void enable() {
        queue.bind(ModelUpdate.class);
        queue.subscribe(this, executor);
    }

    @Override
    public void disable() {
        queue.unsubscribe(this);
    }

    @HandleMessage
    private void onUpdate(ModelUpdate<Punishment> message) {
        final Punishment punishment = message.document();
        if(!punishment.enforced()) {
            announce(punishment);
            enforce(punishment);
        }
    }

    private void enforce(Punishment punishment) {
        players.byUserId(punishment.punished()).ifPresent(punished -> enforce(punishment, punished));
    }

    private void enforce(Punishment punishment, Player punished) {
        final Audience audience = audiences.get(punished);
        switch(punishment.type()) {
            case WARN:
                audience.playSound(new BukkitSound(Sound.ENTITY_ENDERDRAGON_GROWL, 1f, 1f));
                punishmentFormatter.warning(punishment)
                                   .feed((title, subtitle) -> audience.showTitle(title, subtitle, 5, 200, 10));
                break;
            case KICK:
            case BAN:
                playerServerChanger.kickPlayer(punished, punishmentFormatter.screen(punishment, punished));
                break;
        }

        punishmentService.update(punishment._id(), (PunishmentDoc.Enforce) () -> true);
    }

    private void announce(Punishment punishment) {
        players.all()
               .stream()
               .filter(player -> viewable(player, punishment, true))
               .forEach(player -> audiences.get(player).sendMessages(
                   punishmentFormatter.format(punishment, true,
                                              !punishment.server_id().equals(localServer._id()))
               ));
    }

    public boolean viewable(CommandSender sender, Punishment punishment, boolean announced) {
        if(viewByIdentity(sender, punishment)) {
            if(announced) {
                return viewByType(sender, punishment) && viewBySetting(sender, punishment) && viewByIdentity(sender, punishment);
            } else {
                return viewByLookup(sender, punishment);
            }
        }
        return false;
    }

    private boolean viewByIdentity(CommandSender sender, Punishment punishment) {
        return identities.currentOrConsoleIdentity(punishment.punisher()).isRevealed(sender);
    }

    private boolean viewBySetting(CommandSender sender, Punishment punishment) {
        switch(settings.tryManager(sender).map(m -> m.getValue(PunishmentMessageSetting.get(), Options.class, Options.SERVER)).orElse(Options.NONE)) {
            case GLOBAL:
                return true;
            case SERVER:
                return localServer._id().equals(punishment.server_id());
            case NONE:
            default:
                return false;
        }
    }

    private boolean viewByType(CommandSender sender, Punishment punishment) {
        switch(punishment.type()) {
            case WARN:
            case FORUM_BAN:
            case FORUM_WARN:
                return sender.hasPermission(Permissions.STAFF);
            case TOURNEY_BAN:
                return localServer.network().equals(ServerDoc.Network.TOURNAMENT);
            default:
                return true;
        }
    }

    private boolean viewByLookup(CommandSender sender, Punishment punishment) {
        return sender.hasPermission(punishment.stale() ? LOOK_UP_STALE : LOOK_UP);
    }

}
