// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
// Licensed under the GNU Affero General Public License (AGPL).
// See License.AGPL.txt in the project root for license information.

package cmd

import (
	"context"
	"fmt"
	"io/fs"
	"os"
	"time"

	"github.com/cockroachdb/errors"
	"github.com/sirupsen/logrus"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
	"k8s.io/client-go/util/homedir"

	kube "github.com/gitpod-io/gitpod/previewctl/pkg/k8s"
	"github.com/gitpod-io/gitpod/previewctl/pkg/preview"
)

type installContextCmdOpts struct {
	logger *logrus.Logger

	watch              bool
	timeout            time.Duration
	kubeConfigSavePath string
	sshPrivateKeyPath  string

	getCredentialsOpts *getCredentialsOpts
}

func newInstallContextCmd(logger *logrus.Logger) *cobra.Command {
	ctx := context.Background()

	opts := installContextCmdOpts{
		logger: logger,
		getCredentialsOpts: &getCredentialsOpts{
			logger: logger,
		},
	}

	// Used to ensure that we only install contexts
	var lastSuccessfulPreviewEnvironment *preview.Config = nil

	install := func(retry bool, timeout time.Duration) error {
		name, err := preview.GetName(branch)
		if err != nil {
			return err
		}

		if hasAccess(ctx, logger, name) {
			opts.logger.Debugf("Access to [%s] already configured and connections can be established", name)
			return nil
		}

		p, err := preview.New(branch, logger)

		if err != nil {
			return err
		}

		if lastSuccessfulPreviewEnvironment != nil && lastSuccessfulPreviewEnvironment.Same(p) {
			logger.Infof("The preview envrionment hasn't changed")
			return nil
		}

		err = p.InstallContext(ctx, &preview.InstallCtxOpts{
			Retry:             retry,
			RetryTimeout:      opts.timeout,
			KubeSavePath:      opts.kubeConfigSavePath,
			SSHPrivateKeyPath: opts.sshPrivateKeyPath,
		})

		if err == nil {
			lastSuccessfulPreviewEnvironment = p
		}

		return err
	}

	cmd := &cobra.Command{
		Use:   "install-context",
		Short: "Installs the kubectl context of a preview environment.",
		PreRunE: func(cmd *cobra.Command, args []string) error {
			configs, err := opts.getCredentialsOpts.getCredentials(ctx)
			if err != nil {
				return err
			}

			opts.kubeConfigSavePath = getKubeConfigPath()

			err = kube.OutputContext(opts.kubeConfigSavePath, configs)
			if err != nil {
				return err
			}

			if _, err = os.Stat(opts.sshPrivateKeyPath); errors.Is(err, fs.ErrNotExist) {
				return preview.InstallVMSSHKeys()
			}

			return nil
		},
		RunE: func(cmd *cobra.Command, args []string) error {
			if opts.watch {
				for range time.Tick(15 * time.Second) {
					// We're using a short timeout here to handle the scenario where someone switches
					// to a branch that doesn't have a preview environment. In that case the default
					// timeout would mean that we would block for 10 minutes, potentially missing
					// if the user changes to a new branch that does that a preview.
					err := install(true, 30*time.Second)
					if err != nil {
						logger.WithFields(logrus.Fields{"err": err}).Info("Failed to install context. Trying again soon.")
					}
				}
			}

			return install(true, opts.timeout)
		},
	}

	cmd.Flags().BoolVar(&opts.watch, "watch", false, "If watch is enabled, previewctl will keep trying to install the kube-context every 15 seconds, even when successful.")
	cmd.Flags().DurationVarP(&opts.timeout, "timeout", "t", 10*time.Minute, "Timeout before considering the installation failed. It will retry installing the context until successful or the timeout is reached")
	cmd.PersistentFlags().StringVar(&opts.sshPrivateKeyPath, "private-key-path", fmt.Sprintf("%s/.ssh/vm_id_rsa", homedir.HomeDir()), "path to the private key used to authenticate with the VM")
	cmd.PersistentFlags().StringVar(&opts.getCredentialsOpts.serviceAccountPath, "gcp-service-account", viper.GetString("PREVIEW_ENV_DEV_SA_KEY_PATH"), "path to the GCP service account to use")

	return cmd
}
