/*
 * Copyright 2000-2016 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.intellij.remoteServer.util.ssh;

import com.intellij.execution.filters.HyperlinkInfo;
import com.intellij.internal.statistic.UsageTrigger;
import com.intellij.notification.Notification;
import com.intellij.notification.NotificationListener;
import com.intellij.openapi.options.ConfigurationException;
import com.intellij.openapi.options.UnnamedConfigurable;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.MessageType;
import com.intellij.openapi.ui.Messages;
import com.intellij.remoteServer.ServerType;
import com.intellij.remoteServer.configuration.RemoteServer;
import com.intellij.remoteServer.configuration.ServerConfiguration;
import com.intellij.remoteServer.impl.configuration.RemoteServerImpl;
import com.intellij.remoteServer.runtime.ServerConnection;
import com.intellij.remoteServer.runtime.ServerConnectionManager;
import com.intellij.remoteServer.runtime.deployment.DeploymentLogManager;
import com.intellij.remoteServer.runtime.deployment.DeploymentTask;
import com.intellij.remoteServer.runtime.log.LoggingHandler;
import com.intellij.remoteServer.runtime.ui.RemoteServersView;
import com.intellij.remoteServer.util.*;
import com.intellij.ui.HyperlinkLabel;
import org.jetbrains.annotations.NotNull;

import javax.swing.event.HyperlinkEvent;
import javax.swing.event.HyperlinkListener;
import java.io.File;

/**
 * @author michael.golubev
 */
public class SshKeyChecker {

  private static boolean isSshKeyErrorMessage(String errorMessage) {
    return errorMessage.contains("Could not read from remote repository") || errorMessage.contains("The remote end hung up unexpectedly");
  }

  public void checkServerError(String errorMessage,
                               CloudNotifier notifier,
                               Project project,
                               CloudConnectionTask connectionTask) {
    if (isSshKeyErrorMessage(errorMessage)) {
      new ServerHandler(notifier, project, connectionTask).handle(errorMessage);
    }
    else {
      notifier.showMessage(errorMessage, MessageType.ERROR);
    }
  }

  public void checkDeploymentError(String errorMessage,
                                   SshKeyAwareServerRuntime serverRuntime,
                                   DeploymentLogManager logManager,
                                   DeploymentTask deploymentTask) {
    if (isSshKeyErrorMessage(errorMessage) && logManager != null) {
      new DeploymentHandler(serverRuntime, logManager.getMainLoggingHandler(), deploymentTask).handle();
    }
  }

  public <C extends ServerConfiguration> void setupUploadLabel(HyperlinkLabel label,
                                                               UnnamedConfigurable serverConfigurable,
                                                               C serverConfiguration,
                                                               ServerType<C> serverType) {
    new ConfigurableHandler<>(label, serverConfigurable, serverConfiguration, serverType);
  }

  private static class ServerHandler extends HandlerBase {
    private final CloudNotifier myNotifier;
    private final Project myProject;
    private final CloudConnectionTask myConnectionTask;

    private Notification myErrorNotification;

    public ServerHandler(CloudNotifier notifier, Project project, CloudConnectionTask connectionTask) {
      myNotifier = notifier;
      myProject = project;
      myConnectionTask = connectionTask;
    }

    @Override
    public Project getProject() {
      return myProject;
    }

    public void handle(String errorMessage) {
      myNotifier.showMessage(errorMessage + "<br/>You may need to <a href=\"#\">upload SSH public key</a>",
                             MessageType.ERROR,
                             new NotificationListener() {

                               @Override
                               public void hyperlinkUpdate(@NotNull Notification notification, @NotNull HyperlinkEvent event) {
                                 myErrorNotification = notification;
                                 chooseKey();
                               }
                             });
    }

    @Override
    protected void uploadKey(final File sskKey) {
      new CloudConnectionTask(myProject, "Uploading SSH key", myConnectionTask.getServer()) {

        @Override
        protected Object run(CloudServerRuntimeInstance serverRuntime) throws ServerRuntimeException {
          ((SshKeyAwareServerRuntime)serverRuntime).addSshKey(sskKey);
          myErrorNotification.expire();
          myNotifier.showMessage("SSH key was uploaded, you may <a href=\"#\">reconnect</a>",
                                 MessageType.INFO,
                                 new NotificationListener() {

                                   @Override
                                   public void hyperlinkUpdate(@NotNull Notification notification, @NotNull HyperlinkEvent event) {
                                     notification.expire();
                                     reconnect();
                                   }
                                 });
          return null;
        }

        @Override
        protected void runtimeErrorOccurred(@NotNull String errorMessage) {
          myNotifier.showMessage(errorMessage, MessageType.ERROR);
        }
      }.performSync();
    }

    private void reconnect() {
      myConnectionTask.performAsync();
    }
  }

  private static class DeploymentHandler extends HandlerBase {

    private final SshKeyAwareServerRuntime myServerRuntime;
    private final DeploymentTask myDeploymentTask;
    private final LoggingHandler myLoggingHandler;

    private DeploymentHandler(SshKeyAwareServerRuntime serverRuntime,
                              LoggingHandler loggingHandler,
                              DeploymentTask deploymentTask) {
      myServerRuntime = serverRuntime;
      myDeploymentTask = deploymentTask;
      myLoggingHandler = loggingHandler;
    }

    @Override
    protected Project getProject() {
      return myDeploymentTask.getProject();
    }

    public void handle() {
      myLoggingHandler.print("You may need to ");
      myLoggingHandler.printHyperlink("upload SSH public key", new HyperlinkInfo() {

        @Override
        public void navigate(Project project) {
          chooseKey();
        }
      });
      myLoggingHandler.print("\n");
    }

    @Override
    protected void uploadKey(final File sskKey) {
      new CloudRuntimeTask(getProject(), "Uploading SSH key") {

        @Override
        protected CloudServerRuntimeInstance getServerRuntime() {
          return myServerRuntime.asCloudServerRuntime();
        }

        @Override
        protected Object run(CloudServerRuntimeInstance serverRuntimeInstance) throws ServerRuntimeException {
          myServerRuntime.addSshKey(sskKey);

          myLoggingHandler.print("SSH key was uploaded, you may ");
          myLoggingHandler.printHyperlink("redeploy", new HyperlinkInfo() {


            @Override
            public void navigate(Project project) {
              redeploy();
            }
          });
          myLoggingHandler.print("\n");

          return null;
        }

        @Override
        protected void runtimeErrorOccurred(@NotNull String errorMessage) {
          myLoggingHandler.print("Unable to upload SSH key: " + errorMessage + "\n");
        }
      }.performSync();
    }

    private void redeploy() {
      final ServerConnection connection = ServerConnectionManager.getInstance().getOrCreateConnection(myServerRuntime.getServer());

      final RemoteServersView view = RemoteServersView.getInstance(myDeploymentTask.getProject());
      view.showServerConnection(connection);

      connection.deploy(myDeploymentTask, s -> view.showDeployment(connection, (String)s));
    }
  }

  private static class ConfigurableHandler<C extends ServerConfiguration> extends HandlerBase implements HyperlinkListener {

    private final UnnamedConfigurable myServerConfigurable;
    private final C myServerConfiguration;
    private final ServerType<C> myServerType;

    private final HyperlinkLabel myLabel;

    public ConfigurableHandler(HyperlinkLabel label,
                               final UnnamedConfigurable serverConfigurable,
                               C serverConfiguration,
                               ServerType<C> serverType) {
      myServerConfigurable = serverConfigurable;
      myServerConfiguration = serverConfiguration;
      myServerType = serverType;

      label.setHyperlinkText("Upload Public SSH Key");
      label.addHyperlinkListener(this);
      myLabel = label;
    }

    @Override
    public void hyperlinkUpdate(HyperlinkEvent e) {
      chooseKey();
    }

    @Override
    protected void uploadKey(final File sskKey) {
      UsageTrigger.trigger(CloudsTriggerKeys.UPLOAD_SSH_KEY);
      try {
        myServerConfigurable.apply();
      }
      catch (ConfigurationException e) {
        Messages.showErrorDialog("Cannot upload SSH key: " + e.getMessage(), e.getTitle());
        return;
      }

      RemoteServer<C> server = new RemoteServerImpl<>("<temp server to upload ssh key>", myServerType, myServerConfiguration);

      CloudConnectionTask task = new CloudConnectionTask(null, "Uploading SSH key", server) {

        @Override
        protected Object run(CloudServerRuntimeInstance serverRuntime) throws ServerRuntimeException {
          ((SshKeyAwareServerRuntime)serverRuntime).addSshKey(sskKey);
          return null;
        }
      };
      task.performSync();
      task.showMessageDialog(myLabel, "SSH key was uploaded", "Public SSH Key");
    }

    @Override
    protected Project getProject() {
      return null;
    }
  }

  private static abstract class HandlerBase {

    protected void chooseKey() {
      PublicSshKeyDialog dialog = new PublicSshKeyDialog(getProject());
      if (dialog.showAndGet()) {
        uploadKey(dialog.getSshKey());
      }
    }

    protected abstract void uploadKey(File sskKeyFile);

    protected abstract Project getProject();
  }
}
