// 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"
	"path/filepath"

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

	kube "github.com/gitpod-io/gitpod/previewctl/pkg/k8s"
	kctx "github.com/gitpod-io/gitpod/previewctl/pkg/k8s/context"
	"github.com/gitpod-io/gitpod/previewctl/pkg/k8s/context/gke"
	"github.com/gitpod-io/gitpod/previewctl/pkg/k8s/context/harvester"
)

var (
	DefaultKubeConfigPath = filepath.Join(homedir.HomeDir(), clientcmd.RecommendedHomeDir, clientcmd.RecommendedFileName)
)

const (
	coreDevClusterName = "core-dev"
	coreDevProjectID   = "gitpod-core-dev"
	coreDevClusterZone = "europe-west1-b"
)

type getCredentialsOpts struct {
	logger *logrus.Logger

	serviceAccountPath string
	kubeConfigSavePath string
}

func newGetCredentialsCommand(logger *logrus.Logger) *cobra.Command {
	ctx := context.Background()
	opts := &getCredentialsOpts{
		logger: logger,
	}

	cmd := &cobra.Command{
		Use: "get-credentials",
		Long: `previewctl get-credentials retrieves the kubernetes configs for core-dev and harvester clusters,
merges them with the default config, and saves them to the path in KUBECONFIG or the default path '~/.kube/config'"`,
		RunE: func(cmd *cobra.Command, args []string) error {
			configs, err := opts.getCredentials(ctx)
			if err != nil {
				return err
			}

			opts.kubeConfigSavePath = getKubeConfigPath()

			return kube.OutputContext(opts.kubeConfigSavePath, configs)
		},
	}

	cmd.PersistentFlags().StringVar(&opts.serviceAccountPath, "gcp-service-account", viper.GetString("PREVIEW_ENV_DEV_SA_KEY_PATH"), "path to the GCP service account to use")

	return cmd
}

func (o *getCredentialsOpts) getCredentials(ctx context.Context) (*api.Config, error) {
	gkeLoader, err := gke.New(ctx, gke.ConfigLoaderOpts{
		Logger:             o.logger,
		ServiceAccountPath: o.serviceAccountPath,
		Name:               coreDevClusterName,
		ProjectID:          coreDevProjectID,
		Zone:               coreDevClusterZone,
		RenamedContextName: gke.DevContextName,
	})

	if err != nil {
		return nil, errors.Wrap(err, "failed to instantiate gke loader")
	}

	loaderMap := map[string]kctx.Loader{
		gke.DevContextName:    gkeLoader,
		harvester.ContextName: &harvester.ConfigLoader{},
	}

	for _, contextName := range []string{gke.DevContextName, harvester.ContextName} {
		loader := loaderMap[contextName]
		if kc, err := kube.NewFromDefaultConfigWithContext(o.logger, contextName); err == nil && kc.HasAccess(ctx) {
			continue
		}

		kc, err := loader.Load(ctx)
		if err != nil {
			return nil, err
		}

		configs, err := kube.MergeContextsWithDefault(kc)
		if err != nil {
			return nil, err
		}

		// always save the context at the default path
		err = kube.OutputContext(DefaultKubeConfigPath, configs)
		if err != nil {
			return nil, err
		}
	}

	return kube.MergeContextsWithDefault()
}

func hasAccess(ctx context.Context, logger *logrus.Logger, contextName string) bool {
	config, err := kube.NewFromDefaultConfigWithContext(logger, contextName)
	if err != nil {
		if errors.Is(err, kube.ErrContextNotExists) {
			logger.Error(err)
			return false
		}

		logger.Fatal(err)
	}

	return config.HasAccess(ctx)
}

func getKubeConfigPath() string {
	if v := viper.GetString("KUBECONFIG"); v != "" {
		DefaultKubeConfigPath = v
	}

	return DefaultKubeConfigPath
}
