package be.iminds.ilabt.jfed.experimenter_gui;

import be.iminds.ilabt.jfed.experimenter_gui.config.JFedGuiConfig;
import be.iminds.ilabt.jfed.fedmon.webapi.service.json.Server;
import be.iminds.ilabt.jfed.fedmon.webapi.service.json.TriggeredMessage;
import be.iminds.ilabt.jfed.preferences.CorePreferenceKey;
import be.iminds.ilabt.jfed.preferences.JFedPreferences;
import be.iminds.ilabt.jfed.ui.javafx.GlyphUtils;
import be.iminds.ilabt.jfed.util.JFedUtils;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import javafx.scene.control.*;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import org.controlsfx.glyphfont.FontAwesome;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.inject.Inject;
import javax.inject.Singleton;
import java.io.File;
import java.io.IOException;
import java.time.ZonedDateTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * TriggeredMessageHandler this received the TriggeredMessages config and triggers from the GUI.
 * When needed, it will show the appropriate message on these triggers.
 */
@Singleton
public class TriggeredMessageHandler {
    private static final Logger LOG = LoggerFactory.getLogger(TriggeredMessageHandler.class);

    public final JFedPreferences jFedPreferences;
    public final File historyFile;
    public List<TriggeredMessage> triggeredMessages;
    public final Map<Integer, TriggeredMessageHistory> triggeredMessagesHistoryById;

    private static class TriggeredMessageHistory {
        private final Integer triggeredMessageId;
        private boolean dontShowAgainClicked;
        private Date lastShown;

        private TriggeredMessageHistory(Integer triggeredMessageId) {
            this.triggeredMessageId = triggeredMessageId;
            this.dontShowAgainClicked = false;
            this.lastShown = null;
        }

        @JsonCreator
        public TriggeredMessageHistory(@JsonProperty("triggeredMessageId") Integer triggeredMessageId,
                                       @JsonProperty("dontShowAgainClicked") boolean dontShowAgainClicked,
                                       @JsonProperty("lastShown") Date lastShown) {
            this.triggeredMessageId = triggeredMessageId;
            this.dontShowAgainClicked = dontShowAgainClicked;
            this.lastShown = lastShown;
        }

        @JsonProperty
        public Integer getTriggeredMessageId() {
            return triggeredMessageId;
        }

        @JsonProperty
        public boolean isDontShowAgainClicked() {
            return dontShowAgainClicked;
        }

        @JsonProperty
        public Date getLastShown() {
            return lastShown;
        }

        public void setDontShowAgainClicked(boolean dontShowAgainClicked) {
            this.dontShowAgainClicked = dontShowAgainClicked;
        }

        public void setLastShown(Date lastShown) {
            this.lastShown = lastShown;
        }
    }

    @Inject
    public TriggeredMessageHandler(JFedPreferences jFedPreferences, JFedGuiConfig jFedGuiConfig) {
        this.jFedPreferences = jFedPreferences;
        this.triggeredMessagesHistoryById = new HashMap<>();

        //restore triggeredMessagesHistoryById from preferences dir
        String jFedUserDataDir = JFedUtils.getUserDataDirectory();
        if (jFedUserDataDir != null) {
            File userDataDir = new File(jFedUserDataDir);
            this.historyFile = new File(userDataDir, "triggeredMessagesHistory.json");
            if (historyFile.exists()) {
                ObjectMapper mapper = new ObjectMapper();
                try {
                    List<TriggeredMessageHistory> triggeredMessageHistoryList =
                            mapper.readValue(historyFile, new TypeReference<List<TriggeredMessageHistory>>(){});
                    for (TriggeredMessageHistory tmh : triggeredMessageHistoryList) {
                        triggeredMessagesHistoryById.put(tmh.getTriggeredMessageId(), tmh);
                    }
                } catch (IOException e) {
                    LOG.error("Failed to read TriggeredMessageHistory from \""+historyFile.getPath()+"\"", e);
                    //will ignore
                }
            }

        } else {
            this.historyFile = null;
        }

        assert jFedGuiConfig != null;


        this.triggeredMessages = new ArrayList<>(jFedGuiConfig.getTriggeredMessages());
        for (TriggeredMessage triggeredMessage : triggeredMessages) {
            TriggeredMessageHistory history =
                    triggeredMessagesHistoryById.computeIfAbsent(triggeredMessage.getId(),
                            (id) -> new TriggeredMessageHistory(triggeredMessage.getId())
                    );
            if (triggeredMessage.getFrequency() == TriggeredMessage.TriggerFrequency.ONCE_PER_SESSION) {
                history.setLastShown(null);
            }
        }
        LOG.debug("initialised with "+triggeredMessages.size()+" triggeredMessages");
    }

    public void save() {
        if (historyFile == null)
            return;
        LOG.debug("Saving TriggeredMessageHistory to "+historyFile);

        ObjectMapper mapper = new ObjectMapper();
        try {
            List<TriggeredMessageHistory> triggeredMessageHistoryList = new ArrayList<>(triggeredMessagesHistoryById.values());
            mapper.writeValue(historyFile, triggeredMessageHistoryList);
        } catch (IOException e) {
            LOG.error("Failed to write TriggeredMessageHistory to \""+historyFile.getPath()+"\"", e);
            //will ignore
        }
    }

    public void onEvent(TriggeredMessage.GuiTrigger guiTrigger) {
        onEvent(guiTrigger, Collections.emptyList());
    }
    public void onEvent(TriggeredMessage.GuiTrigger guiTrigger, List<Server> involvedServers) {
        List<Integer> involvedServerIds;
        if (involvedServers != null) {
            involvedServerIds = involvedServers.stream().
                    filter(Objects::nonNull).
                    map(Server::getId).
                    filter(Objects::nonNull).
                    collect(Collectors.toList());
        } else {
            involvedServerIds = Collections.emptyList();
        }
        LOG.debug("onEvent("+guiTrigger+", "+involvedServerIds+")");
        try {
            for (TriggeredMessage triggeredMessage : triggeredMessages) {
                if (guiTrigger == triggeredMessage.getGuiTrigger()) {
                    TriggeredMessageHistory history =
                            triggeredMessagesHistoryById.computeIfAbsent(triggeredMessage.getId(),
                                    (id) -> new TriggeredMessageHistory(triggeredMessage.getId())
                            );

                    boolean trigger = true;
                    if (triggeredMessage.getOnlyTriggerForServer() != null) {
                        if (!involvedServerIds.contains(triggeredMessage.getOnlyTriggerForServer()))
                            trigger = false;
                    }
                    if (triggeredMessage.getOnlyTriggerOnProxyForJFedState() != null) {
                        String pref = jFedPreferences.getString(CorePreferenceKey.PREF_SSHPROXY_USE_FOR_JFED); //AUTO, ALWAYS or NEVER (or null)
                        if (triggeredMessage.getOnlyTriggerOnProxyForJFedState() == true &&
                                (!Objects.equals(pref, "ALWAYS")))
                            trigger = false;
                        if (triggeredMessage.getOnlyTriggerOnProxyForJFedState() == false &&
                                (!Objects.equals(pref, "NEVER")))
                            trigger = false;
                    }
                    if (triggeredMessage.getOnlyTriggerOnProxyForSshState() != null) {
                        String pref = jFedPreferences.getString(CorePreferenceKey.PREF_SSHPROXY_USE_FOR_SSH); //AUTO, ALWAYS or NEVER (or null)
                        if (triggeredMessage.getOnlyTriggerOnProxyForSshState() == true &&
                                (!Objects.equals(pref, "ALWAYS")))
                            trigger = false;
                        if (triggeredMessage.getOnlyTriggerOnProxyForSshState() == false &&
                                (!Objects.equals(pref, "NEVER")))
                            trigger = false;
                    }
                    if (triggeredMessage.getEnableDoNotShowAgainButton() && history.dontShowAgainClicked)
                        trigger = false;
                    if (triggeredMessage.getFrequency() != null && history.getLastShown() != null) {
                        switch (triggeredMessage.getFrequency()) {
                            case ALWAYS: break;
                            case ONCE: { trigger = false; break; }
                            case ONCE_PER_SESSION: { trigger = false; break; }
                            case DAILY: {
                                ZonedDateTime zonedDateTime = ZonedDateTime.now();
                                ZonedDateTime dayStart = zonedDateTime.toLocalDate().atStartOfDay(zonedDateTime.getZone());
                                if (history.getLastShown().after(Date.from(dayStart.toInstant()))) {
                                    trigger = false;
                                }

                                break;
                            }
                        }
                    }

                    if (trigger)
                        doTrigger(triggeredMessage, history);
                }
            }

            if (guiTrigger == TriggeredMessage.GuiTrigger.EXIT)
                save();
        } catch (AssertionError e) {
            LOG.error("AssertionError while processing a TriggerMessage event. Will return to caller to prevent GUI problems", e);
        } catch (Exception e) {
            LOG.error("Exception while processing a TriggerMessage event. Will return to caller to prevent GUI problems", e);
        }

    }

    private void doTrigger(TriggeredMessage triggeredMessage, TriggeredMessageHistory history) {
        VBox pane = new VBox();
        pane.setSpacing(5);

        Label textLabel = new Label(triggeredMessage.getText());
        textLabel.setWrapText(true);
        pane.getChildren().add(textLabel);
        final CheckBox checkBox;
        if (triggeredMessage.getEnableDoNotShowAgainButton() == Boolean.TRUE) {
            checkBox = new CheckBox("Do not show this dialog again");
            pane.getChildren().add(checkBox);
        } else {
            checkBox = null;
        }

        DialogPane dialogPane = new DialogPane();
        dialogPane.setHeaderText(triggeredMessage.getTitle());

        TriggeredMessage.MessageType type = triggeredMessage.getType();
        if (type == null)
            type = TriggeredMessage.MessageType.INFO;
        switch (type) {
            case WARNING: { dialogPane.setGraphic(GlyphUtils.createDialogGlyph(FontAwesome.Glyph.WARNING, Color.ORANGE)); break; }
            case ERROR: { dialogPane.setGraphic(GlyphUtils.createDialogGlyph(FontAwesome.Glyph.TIMES_CIRCLE, Color.RED)); break; }
            case INFO: { dialogPane.setGraphic(GlyphUtils.createDialogGlyph(FontAwesome.Glyph.INFO_CIRCLE, Color.BLUE)); break; }
        }

        dialogPane.setContent(pane);
        dialogPane.getButtonTypes().setAll(ButtonType.OK);

        Dialog<ButtonType> dialog = new Dialog<>();
        dialog.setTitle(triggeredMessage.getTitle());
        dialog.setDialogPane(dialogPane);

        Optional<ButtonType> result = dialog.showAndWait();

        if (checkBox != null) {
            history.setDontShowAgainClicked(checkBox.isSelected());
        }

        history.setLastShown(new Date());
    }
}
