package stream.flarebot.flarebot.scheduler;

import com.datastax.driver.core.PreparedStatement;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.Period;
import stream.flarebot.flarebot.FlareBot;
import stream.flarebot.flarebot.FlareBotManager;
import stream.flarebot.flarebot.Getters;
import stream.flarebot.flarebot.database.CassandraController;
import stream.flarebot.flarebot.mod.modlog.ModAction;
import stream.flarebot.flarebot.mod.modlog.ModlogHandler;
import stream.flarebot.flarebot.objects.GuildWrapper;
import stream.flarebot.flarebot.util.MessageUtils;
import stream.flarebot.flarebot.util.general.FormatUtils;
import stream.flarebot.flarebot.util.general.GuildUtils;

public class FutureAction {

    private static PreparedStatement update;
    private static PreparedStatement delete;

    /*
     * Ok so this will work with a few things, due to this it will have quite a few weird fields.
     *
     * CREATE TABLE future_tasks (guild_id bigint, channel_id bigint, responsible bigint, content text,
     *                            expires_at timestamp, created_at timestamp, action varchar)
     */

    /**
     * Guild ID it was executed in
     */
    private long guildId;
    /**
     * Channel ID it was executed in
     */
    private long channelId;
    /**
     * ID of user who ran the command - Could be a mod or normal user.
     */
    private long responsible;
    /**
     * ID of the target user - if applicable
     */
    private long target;
    /**
     * Content - This could be a reason or just a general message.
     */
    private String content;
    /**
     * How long until the action will be executed.
     */
    private Period delay;
    /**
     * When the task expires. This is calculated from the delay, it will be added onto the current time.
     */
    private DateTime expires;
    /**
     * When the task was created
     */
    private DateTime created;
    /**
     * Rest action - This could be a role remove, a message being sent etc.
     */
    private Action action;

    public FutureAction(long guildId, long channelId, long responsible, long target, String content,
                        DateTime expires, DateTime created, Action action) {
        this.guildId = guildId;
        this.channelId = channelId;
        this.responsible = responsible;
        this.target = target;
        this.content = content;
        this.expires = expires;
        this.created = created;
        this.action = action;

        if (expires.minus(created.getMillis()).getMillis() > Integer.MAX_VALUE) {
            delete();
            return;
        }
        this.delay = new Period(expires.minus(created.getMillis()).getMillis());
    }

    public FutureAction(long guildId, long channelId, long responsible, long target, String content,
                        Period delay, Action action) {
        this.guildId = guildId;
        this.channelId = channelId;
        this.responsible = responsible;
        this.target = target;
        this.content = content;
        this.delay = delay;
        this.created = new DateTime(DateTimeZone.UTC);
        this.expires = created.plus(delay);
        this.action = action;

        if (delay.getMillis() <= 0)
            delete();
    }

    public FutureAction(long guildId, long channelId, long responsible, String content, Period delay, Action action) {
        this.guildId = guildId;
        this.channelId = channelId;
        this.responsible = responsible;
        this.target = -1;
        this.content = content;
        this.delay = delay;
        this.created = new DateTime(DateTimeZone.UTC);
        this.expires = created.plus(delay);
        this.action = action;
    }

    public long getGuildId() {
        return guildId;
    }

    public long getChannelId() {
        return channelId;
    }

    public long getResponsible() {
        return responsible;
    }

    public long getTarget() {
        return this.target;
    }

    public String getContent() {
        return content;
    }

    public DateTime getExpires() {
        return expires;
    }

    public DateTime getCreated() {
        return created;
    }

    public Action getAction() {
        return action;
    }

    public void execute() {
        GuildWrapper gw = FlareBotManager.instance().getGuild(String.valueOf(guildId));
        if (gw == null || gw.getGuild() == null) return;
        switch (action) {
            case TEMP_MUTE:
                if (gw.getGuild().getTextChannelById(channelId) != null) {
                    ModlogHandler.getInstance().handleAction(gw,
                            gw.getGuild().getTextChannelById(channelId),
                            null,
                            GuildUtils.getUser(String.valueOf(target), String.valueOf(guildId), true),
                            ModAction.UNMUTE,
                            "Temporary mute expired, was muted for " + FormatUtils.formatJodaTime(delay)
                    );
                } else
                    gw.getModeration().unmuteUser(gw, gw.getGuild().getMemberById(target));
                break;
            case TEMP_BAN:
                if (gw.getGuild().getTextChannelById(channelId) != null) {
                    ModlogHandler.getInstance().handleAction(gw,
                            gw.getGuild().getTextChannelById(channelId),
                            null,
                            GuildUtils.getUser(String.valueOf(target), String.valueOf(guildId), true),
                            ModAction.UNBAN,
                            "Temporary ban expired, was banned for " + FormatUtils.formatJodaTime(delay)
                    );
                } else
                    gw.getGuild().getController().unban(String.valueOf(target)).queue();
                break;
            case REMINDER:
                if (Getters.getChannelById(channelId) != null)
                    Getters.getChannelById(channelId).sendMessage(Getters
                            .getUserById(responsible).getAsMention() + " You asked me to remind you " +
                            FormatUtils.formatJodaTime(delay).toLowerCase() + " ago about: `" + content.replaceAll("`", "'") + "`")
                            .queue();
                break;
            case DM_REMINDER:
                MessageUtils.sendPM(Getters.getChannelById(channelId), Getters.getUserById(responsible), Getters.getUserById(responsible).getAsMention() + " You asked me to remind you " +
                        FormatUtils.formatJodaTime(delay).toLowerCase() + " ago about: `" + content.replaceAll("`", "'") + "`");
                break;
            default:
                break;
        }
        delete();
    }

    public void queue() {
        // I have to minus here since this has the complete end time.
        Scheduler.delayTask(this::execute, "FutureTask-" + action.name() + "-" + expires.toString(),
                getExpires().minus(System.currentTimeMillis()).getMillis());
        if (update == null) update = CassandraController.prepare("UPDATE flarebot.future_tasks SET responsible = ?, " +
                "target = ?, content = ?, expires_at = ?, action = ? WHERE guild_id = ? AND channel_id = ? " +
                "AND created_at = ?");
        CassandraController.executeAsync(update.bind().setLong(0, responsible).setLong(1, target).setString(2, content)
                .setTimestamp(3, expires.toDate()).setString(4, action.name()).setLong(5, guildId).setLong(6, channelId)
                .setTimestamp(7, created.toDate()));
        FlareBot.instance().getFutureActions().add(this);
    }

    public void delete() {
        FlareBot.instance().getFutureActions().remove(this);
        if (delete == null)
            delete = CassandraController.prepare("DELETE FROM flarebot.future_tasks WHERE guild_id = ? " +
                    "AND channel_id = ? AND created_at = ?");
        CassandraController.executeAsync(delete.bind().setLong(0, guildId).setLong(1, channelId)
                .setTimestamp(2, created.toDate()));
        Scheduler.cancelTask("FutureTask-" + action.name() + "-" + expires.toString());
    }

    public enum Action {
        TEMP_MUTE,
        TEMP_BAN,
        REMINDER,
        DM_REMINDER
    }
}