package com.airlock.iam.plugin.configeditor.ui.components;

import com.airlock.iam.admin.domain.model.session.AdminappSession;
import com.airlock.iam.core.misc.authorization.AccessControllerException;
import com.airlock.iam.plugin.api.config.ConfigurationEnvironment;
import com.airlock.iam.plugin.api.config.Environment;
import com.airlock.iam.plugin.api.exception.ConfigurationException;
import com.airlock.iam.plugin.configeditor.ApplicationRootInfo;
import com.airlock.iam.plugin.configeditor.ConfigEditorApplication;
import com.airlock.iam.plugin.configeditor.ConfigurationLoadListener;
import com.airlock.iam.plugin.configeditor.data.ConfigContainer;
import com.airlock.iam.plugin.configeditor.data.ConfigEditorState;
import com.airlock.iam.plugin.configeditor.session.ConfigEditorSessionAccessor;
import com.airlock.iam.plugin.configeditor.ui.DownloadConfigurationResource;
import com.airlock.iam.plugin.configeditor.ui.dialog.ExportConfigurationDialog;
import com.airlock.iam.plugin.configeditor.ui.dialog.SecureExternalStorageManager;
import com.airlock.iam.plugin.configeditor.util.GuiResources;
import com.airlock.iam.plugin.framework.application.service.IncludeAllFilter;
import com.airlock.iam.sensitivevalues.application.service.SensitiveValuesProvider;
import com.airlock.iam.system.property.IamSystemProperties;
import com.vaadin.event.LayoutEvents.LayoutClickEvent;
import com.vaadin.event.LayoutEvents.LayoutClickListener;
import com.vaadin.server.FileDownloader;
import com.vaadin.server.Sizeable.Unit;
import com.vaadin.shared.ui.ContentMode;
import com.vaadin.shared.ui.MarginInfo;
import com.vaadin.ui.Alignment;
import com.vaadin.ui.Component;
import com.vaadin.ui.Embedded;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.Label;
import com.vaadin.ui.Notification;
import com.vaadin.ui.UI;
import com.vaadin.ui.VerticalLayout;
import com.vaadin.ui.Notification.Type;
import java.io.File;
import java.util.Optional;
import java.util.Set;
import java.util.function.Supplier;

public final class HeaderUI extends HorizontalLayout implements LayoutClickListener, ConfigurationLoadListener {
   private Label configFileLabel;
   private Embedded configWarningIcon;
   private IconWithLabel reloadIcon;
   private IconWithLabel applyIcon;
   private IconWithLabel restoreIcon;
   private IconWithLabel saveIcon;
   private IconWithLabel newConfigIcon;
   private IconWithLabel uploadConfigIcon;
   private IconWithLabel downloadConfigIcon;
   private IconWithLabel secureExternalStorageManagerIcon;
   private HorizontalLayout secureExternalStorageManagerIconLayout;
   private SensitiveValuesProvider sensitiveValuesProvider;
   private final ValidationToolbar validationToolbar;
   private final ConfigurationEnvironmentSelector environmentSelector;
   private final Optional<ActivateButtonUpdater> activateButtonUpdater;
   private FileDownloader configFileDownloader;
   private final ApplicationRootInfo applicationRootInfo;

   public HeaderUI(ConfigEditorState state, ApplicationRootInfo applicationRootInfo) {
      this.environmentSelector = new ConfigurationEnvironmentSelector(state.getEnvironmentSelection(), state.getInstanceEnvironment());
      this.validationToolbar = new ValidationToolbar(state.getEnvironmentSelection(), state.getInstanceEnvironment());
      this.configureComponent();
      this.addComponent(this.makeHeader());
      this.setVisibilities();
      this.applicationRootInfo = applicationRootInfo;
      if (this.isApplyConfigAllowed()) {
         this.activateButtonUpdater = Optional.of(new ActivateButtonUpdater(this.applyIcon, state.getInstanceEnvironment()));
         state.getEnvironmentSelection().addListener(this.activateButtonUpdater.get());
      } else {
         this.activateButtonUpdater = Optional.empty();
      }

      state.getEnvironmentSelection().addListener(new ExternalSecretButtonUpdater(this.secureExternalStorageManagerIcon, state.getInstanceEnvironment()));
   }

   @Override
   public void newConfigurationLoaded(ConfigurationLoadListener.ConfigurationLoadedEvent event) {
      this.activateButtonUpdater.ifPresent(updater -> updater.newConfigurationLoaded(event));
   }

   @Override
   public void failedToLoadConfig(ConfigurationLoadListener.FailedToLoadConfigurationEvent event) {
      this.activateButtonUpdater.ifPresent(updater -> updater.failedToLoadConfig(event));
   }

   private boolean isApplyConfigAllowed() {
      Optional<AdminappSession> session = ConfigEditorSessionAccessor.getCachedAdminappSession();

      try {
         return session.isPresent()
            && session.get().isAuthenticated()
            && this.applicationRootInfo.isApplyConfigAllowedFor(session.get().getAuthentee().orElseThrow());
      } catch (AccessControllerException e) {
         return false;
      }
   }

   private void configureComponent() {
      this.setSpacing(false);
      this.setWidth("100%");
      this.addStyleName("headerui");
      this.setMargin(new MarginInfo(false, false, false, true));
   }

   private void setVisibilities() {
      this.setConfigDependentHeaderIconsVisibility(false, false);
   }

   private VerticalLayout makeHeader() {
      VerticalLayout leftHeader = new VerticalLayout();
      leftHeader.setMargin(false);
      leftHeader.addComponent(this.makeHeaderLine());
      leftHeader.addComponent(this.makeToolbar());
      return leftHeader;
   }

   private HorizontalLayout makeHeaderLine() {
      this.configFileLabel = new Label("");
      this.configFileLabel.setContentMode(ContentMode.HTML);
      this.configFileLabel.setId("configFileLabel");
      this.configWarningIcon = new Embedded(null, GuiResources.ICON_VALIDATION_WARNING);
      this.configWarningIcon.setWidth(16.0F, Unit.PIXELS);
      this.configWarningIcon.setVisible(false);
      HorizontalLayout instanceLabelLayout = new HorizontalLayout(new Component[]{this.configFileLabel, this.configWarningIcon});
      HorizontalLayout hl = new HorizontalLayout();
      hl.setSpacing(false);
      hl.addStyleName("toolbar-header");
      hl.setWidth("100%");
      hl.addComponent(instanceLabelLayout);
      return hl;
   }

   private HorizontalLayout makeToolbar() {
      HorizontalLayout toolbarWithLogo = new HorizontalLayout();
      toolbarWithLogo.addStyleName("toolbar");
      toolbarWithLogo.setWidth(100.0F, Unit.PERCENTAGE);
      HorizontalLayout iconToolBar = this.makeIconToolbar();
      HorizontalLayout environmentSelectionLayout = this.environmentSelector.getLayout();
      HorizontalLayout validationUI = this.validationToolbar.getLayout();
      HorizontalLayout uiElements = new HorizontalLayout();
      uiElements.setSpacing(false);
      uiElements.setWidth("100%");
      uiElements.addComponent(iconToolBar);
      uiElements.setComponentAlignment(iconToolBar, Alignment.MIDDLE_LEFT);
      uiElements.addComponent(environmentSelectionLayout);
      uiElements.setComponentAlignment(environmentSelectionLayout, Alignment.MIDDLE_RIGHT);
      uiElements.addComponent(validationUI);
      uiElements.setComponentAlignment(validationUI, Alignment.MIDDLE_RIGHT);
      uiElements.setExpandRatio(iconToolBar, 2.0F);
      uiElements.setExpandRatio(environmentSelectionLayout, 1.0F);
      uiElements.setExpandRatio(validationUI, 1.0F);
      uiElements.addStyleName("toolbar-elements");
      toolbarWithLogo.addComponent(uiElements);
      return toolbarWithLogo;
   }

   public void setConfigFileLabel(String label) {
      this.configFileLabel.setValue(label);
   }

   public void updateConfigWarning(ConfigurationEnvironment instanceEnvironment, Set<ConfigurationEnvironment> environmentsFromConfig) {
      if (instanceEnvironment.isCommonEnvironment() && environmentsFromConfig.size() > 1) {
         this.configWarningIcon
            .setDescription(
               "The configuration contains environment specific values but your IAM instance does not support environments. The configuration can therefore not be activated. Enable environments by editing the instance configuration."
            );
         this.configWarningIcon.setVisible(true);
      } else {
         this.configWarningIcon.setVisible(false);
      }
   }

   private HorizontalLayout makeIconToolbar() {
      HorizontalLayout toolbar = new HorizontalLayout();
      toolbar.addStyleName("toolbar-buttons");
      toolbar.setWidth("100%");
      this.applyIcon = new IconWithLabel(
         GuiResources.ICON_APPLY,
         GuiResources.ICON_APPLY_DISABLED,
         "Activate",
         "Apply the current configuration in the editor to the running Airlock IAM modules.",
         this
      );
      this.applyIcon.disable();
      this.applyIcon.setId("applyIcon");
      toolbar.addComponent(this.applyIcon);
      toolbar.setExpandRatio(this.applyIcon, 1.0F);
      this.reloadIcon = new IconWithLabel(
         GuiResources.ICON_RELOAD,
         GuiResources.ICON_RELOAD_DISABLED,
         "Reload",
         "Discard open changes and re-load the active configuration (= reset GUI to reflect active configuration).",
         this
      );
      this.reloadIcon.disable();
      toolbar.addComponent(this.reloadIcon);
      toolbar.setExpandRatio(this.reloadIcon, 1.0F);
      this.reloadIcon.setId("reloadIcon");
      this.addSpace(toolbar);
      this.newConfigIcon = new IconWithLabel(
         GuiResources.ICON_NEW_CONFIG, GuiResources.ICON_NEW_CONFIG_DISABLED, "New", "Choose a configuration template to create a new one.", this
      );
      this.newConfigIcon.setId("newConfigIcon");
      toolbar.addComponent(this.newConfigIcon);
      toolbar.setExpandRatio(this.newConfigIcon, 1.0F);
      this.uploadConfigIcon = new IconWithLabel(
         GuiResources.ICON_UPLOAD_CONFIG, GuiResources.ICON_UPLOAD_CONFIG_DISABLED, "Upload", "Upload a configuration.", this
      );
      this.uploadConfigIcon.setId("uploadConfigIcon");
      toolbar.addComponent(this.uploadConfigIcon);
      toolbar.setExpandRatio(this.uploadConfigIcon, 1.0F);
      this.downloadConfigIcon = new IconWithLabel(
         GuiResources.ICON_DOWNLOAD_CONFIG, GuiResources.ICON_DOWNLOAD_CONFIG_DISABLED, "Download", "Download the current configuration.", this
      );
      toolbar.addComponent(this.downloadConfigIcon);
      toolbar.setExpandRatio(this.downloadConfigIcon, 1.0F);
      this.downloadConfigIcon.setId("downloadConfigIcon");
      this.addSpace(toolbar);
      this.saveIcon = new IconWithLabel(GuiResources.ICON_SAVE_WORKINGCOPY, GuiResources.ICON_SAVE_WORKINGCOPY_DISABLED, "Save", "Save a working copy.", this);
      this.saveIcon.disable();
      this.saveIcon.setId("saveIcon");
      toolbar.addComponent(this.saveIcon);
      toolbar.setExpandRatio(this.saveIcon, 1.0F);
      this.restoreIcon = new IconWithLabel(
         GuiResources.RESTORE_HISTORY, GuiResources.RESTORE_HISTORY_DISABLED, "Restore", "Restore a previously saved working copy.", this
      );
      this.restoreIcon.disable();
      this.restoreIcon.setId("restoreIcon");
      toolbar.addComponent(this.restoreIcon);
      toolbar.setExpandRatio(this.restoreIcon, 1.0F);
      this.addSpace(toolbar);
      this.secureExternalStorageManagerIcon = new IconWithLabel(
         GuiResources.ICON_SECURE_STORAGE_MANAGER,
         GuiResources.ICON_SECURE_STORAGE_MANAGER_DISABLED,
         "External Secrets",
         "Manage externally stored secrets.",
         this
      );
      this.secureExternalStorageManagerIcon.setId("secureExternalStorageManagerIcon");
      Label secureStorageManagerSpacer = new Label();
      this.secureExternalStorageManagerIconLayout = new HorizontalLayout(new Component[]{this.secureExternalStorageManagerIcon, secureStorageManagerSpacer});
      this.secureExternalStorageManagerIconLayout.setSpacing(false);
      this.secureExternalStorageManagerIconLayout.setVisible(false);
      toolbar.addComponent(this.secureExternalStorageManagerIconLayout);
      toolbar.setExpandRatio(this.secureExternalStorageManagerIconLayout, 1.0F);
      return toolbar;
   }

   public void setConfigDependentHeaderIconsVisibility(boolean configLoaded, boolean isSaveAllowed) {
      if (configLoaded) {
         this.downloadConfigIcon.enable();
         this.reloadIcon.enable();
         if (isSaveAllowed) {
            this.saveIcon.enable();
         } else {
            this.saveIcon.disable();
         }

         this.restoreIcon.enable();
      } else {
         this.downloadConfigIcon.disable();
         this.reloadIcon.disable();
         this.saveIcon.disable();
         this.restoreIcon.disable();
      }
   }

   private void addSpace(HorizontalLayout component) {
      Label space = new Label();
      component.addComponent(space);
      component.setExpandRatio(space, 0.1F);
   }

   public void layoutClick(LayoutClickEvent event) {
      ConfigEditorApplication app = this.getConfigEditorApplication();
      ConfigContainer configContainer = app.getConfigContainer();
      if (event.getSource() == this.reloadIcon) {
         if (app.getConfigFileToLoad().isPresent()) {
            app.loadConfig(new File(app.getConfigFileToLoad().get()), IamSystemProperties.activeConfigFormat(), false);
            configContainer.deleteDraft();
         } else {
            Notification.show("No active configuration file found.", Type.ERROR_MESSAGE);
         }
      } else if (event.getSource() == this.applyIcon) {
         app.applyConfiguration();
      } else if (event.getSource() == this.saveIcon) {
         app.showSaveWorkingCopyDialog();
      } else if (event.getSource() == this.restoreIcon) {
         app.showLoadWorkingCopyDialog();
      } else if (event.getSource() == this.newConfigIcon) {
         app.showLoadConfigTemplateDialog();
      } else if (event.getSource() == this.uploadConfigIcon) {
         app.uploadConfiguration();
      } else if (event.getSource() == this.downloadConfigIcon) {
         if (configContainer.getDictionary().getConfigurationEnvironments().size() > 1) {
            ExportConfigurationDialog dialog = new ExportConfigurationDialog(configContainer);
            app.addWindow(dialog);
         }
      } else if (event.getSource() == this.secureExternalStorageManagerIcon) {
         new SecureExternalStorageManager(SecureExternalStorageManager.ADD_NEW_ID, this.sensitiveValuesProvider).show();
      }
   }

   public void enableDownloadConfigMenuItem(boolean enable) {
      if (enable) {
         this.downloadConfigIcon.enable();
      } else {
         this.downloadConfigIcon.disable();
      }
   }

   public void disableValidationIcons() {
      this.validationToolbar.disableValidationIcons();
   }

   public boolean isValidationSelectionInErrorState() {
      return this.validationToolbar.isValidationSelectionInErrorState();
   }

   public boolean isValidationSelectionInSuccessfulState() {
      return this.validationToolbar.isValidationSelectionInSuccessfulState();
   }

   public void setValidationSelectionSuccessful() {
      this.validationToolbar.setValidationSelectionSuccessful();
   }

   public void setValidationSelectionError(Set<ConfigurationException> exceptions) {
      this.validationToolbar.setValidationSelectionError(exceptions);
   }

   public void disableValidationSelectionIcon() {
      this.validationToolbar.disableValidationSelectionIcon();
   }

   public void disableValidationGlobalIcon() {
      this.validationToolbar.disableValidationGlobalIcon();
   }

   public void setValidationGlobalSuccessful() {
      this.validationToolbar.setValidationGlobalSuccessful();
   }

   public void setValidationGlobalError(Set<ConfigurationException> exceptions) {
      this.validationToolbar.setValidationGlobalError(exceptions);
   }

   public Environment getSelectedValidationEnvironment() {
      return this.validationToolbar.getSelectedValidationEnvironment();
   }

   private ConfigEditorApplication getConfigEditorApplication() {
      return (ConfigEditorApplication)UI.getCurrent();
   }

   public void setSensitiveValuesProvider(SensitiveValuesProvider sensitiveValuesProvider) {
      this.sensitiveValuesProvider = sensitiveValuesProvider;
      this.secureExternalStorageManagerIconLayout.setVisible(true);
   }

   public void initEnvironmentSelection(Set<ConfigurationEnvironment> configurationEnvironments) {
      this.environmentSelector.init(configurationEnvironments);
   }

   public void updateDownloadConfigurationResource() {
      ConfigContainer configContainer = this.getConfigEditorApplication().getConfigContainer();
      int nrOfEnvs = configContainer == null ? 0 : configContainer.getDictionary().getConfigurationEnvironments().size();
      if (nrOfEnvs == 1 && this.configFileDownloader == null) {
         Supplier<ConfigContainer> configFileHandlerSupplier = () -> this.getConfigEditorApplication().getConfigContainer();
         this.configFileDownloader = new FileDownloader(new DownloadConfigurationResource(configFileHandlerSupplier, IncludeAllFilter::new));
         this.configFileDownloader.extend(this.downloadConfigIcon);
      } else if (nrOfEnvs != 1 && this.configFileDownloader != null) {
         this.configFileDownloader.remove();
         this.configFileDownloader = null;
      }
   }
}
