package commands

import (
	"fmt"
	"os"

	"github.com/coreos/go-oidc/v3/oidc"
	log "github.com/sirupsen/logrus"
	"github.com/spf13/cobra"

	"github.com/argoproj/argo-cd/v3/cmd/argocd/commands/headless"
	argocdclient "github.com/argoproj/argo-cd/v3/pkg/apiclient"
	settingspkg "github.com/argoproj/argo-cd/v3/pkg/apiclient/settings"
	"github.com/argoproj/argo-cd/v3/util/errors"
	utilio "github.com/argoproj/argo-cd/v3/util/io"
	jwtutil "github.com/argoproj/argo-cd/v3/util/jwt"
	"github.com/argoproj/argo-cd/v3/util/localconfig"
	"github.com/argoproj/argo-cd/v3/util/session"
)

// NewReloginCommand returns a new instance of `argocd relogin` command
func NewReloginCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
	var (
		password         string
		callback         string
		ssoPort          int
		ssoLaunchBrowser bool
	)
	command := &cobra.Command{
		Use:   "relogin",
		Short: "Refresh an expired authenticate token",
		Long:  "Refresh an expired authenticate token",
		Run: func(c *cobra.Command, args []string) {
			ctx := c.Context()

			if len(args) != 0 {
				c.HelpFunc()(c, args)
				os.Exit(1)
			}
			localCfg, err := localconfig.ReadLocalConfig(clientOpts.ConfigPath)
			errors.CheckError(err)
			if localCfg == nil {
				log.Fatal("No context found. Login using `argocd login`")
			}
			configCtx, err := localCfg.ResolveContext(localCfg.CurrentContext)
			errors.CheckError(err)

			var tokenString string
			var refreshToken string
			reloginOpts := argocdclient.ClientOptions{
				ConfigPath:        "",
				ServerAddr:        configCtx.Server.Server,
				Insecure:          configCtx.Server.Insecure,
				ClientCertFile:    clientOpts.ClientCertFile,
				ClientCertKeyFile: clientOpts.ClientCertKeyFile,
				GRPCWeb:           clientOpts.GRPCWeb,
				GRPCWebRootPath:   clientOpts.GRPCWebRootPath,
				PlainText:         configCtx.Server.PlainText,
				Headers:           clientOpts.Headers,
			}
			acdClient := headless.NewClientOrDie(&reloginOpts, c)
			claims, err := configCtx.User.Claims()
			errors.CheckError(err)
			if jwtutil.StringField(claims, "iss") == session.SessionManagerClaimsIssuer {
				fmt.Printf("Relogging in as '%s'\n", userDisplayName(claims))
				tokenString = passwordLogin(ctx, acdClient, localconfig.GetUsername(jwtutil.StringField(claims, "sub")), password)
			} else {
				fmt.Println("Reinitiating SSO login")
				setConn, setIf := acdClient.NewSettingsClientOrDie()
				defer utilio.Close(setConn)
				httpClient, err := acdClient.HTTPClient()
				errors.CheckError(err)
				ctx = oidc.ClientContext(ctx, httpClient)
				acdSet, err := setIf.Get(ctx, &settingspkg.SettingsQuery{})
				errors.CheckError(err)
				oauth2conf, provider, err := acdClient.OIDCConfig(ctx, acdSet)
				errors.CheckError(err)
				tokenString, refreshToken = oauth2Login(ctx, callback, ssoPort, acdSet.GetOIDCConfig(), oauth2conf, provider, ssoLaunchBrowser)
			}

			localCfg.UpsertUser(localconfig.User{
				Name:         localCfg.CurrentContext,
				AuthToken:    tokenString,
				RefreshToken: refreshToken,
			})
			err = localconfig.WriteLocalConfig(*localCfg, clientOpts.ConfigPath)
			errors.CheckError(err)
			fmt.Printf("Context '%s' updated\n", localCfg.CurrentContext)
		},
		Example: `
# Reinitiates the login with previous contexts
argocd relogin

# Reinitiates the login with password
argocd relogin --password YOUR_PASSWORD

# Configure direct access using Kubernetes API server
argocd login cd.argoproj.io --core

# If user logged in with - "argocd login cd.argoproj.io" with sso login
# The command - "argocd relogin" will Reinitiates SSO login and updates the server context`,
	}
	command.Flags().StringVar(&password, "password", "", "The password of an account to authenticate")
	command.Flags().IntVar(&ssoPort, "sso-port", DefaultSSOLocalPort, "Port to run local OAuth2 login application")
	command.Flags().StringVar(&callback, "callback", "", "Host and Port for the callback URL")
	command.Flags().BoolVar(&ssoLaunchBrowser, "sso-launch-browser", true, "Automatically launch the default browser when performing SSO login")
	return command
}
