package cli

import (
	"errors"
	"fmt"
	"net/http"
	"os/signal"
	"time"

	"github.com/spf13/cobra"
	"golang.org/x/xerrors"

	"github.com/coder/coder/cli/cliui"
	"github.com/coder/coder/coderd/gitauth"
	"github.com/coder/coder/codersdk"
	"github.com/coder/retry"
)

// gitAskpass is used by the Coder agent to automatically authenticate
// with Git providers based on a hostname.
func gitAskpass() *cobra.Command {
	return &cobra.Command{
		Use:    "gitaskpass",
		Hidden: true,
		Args:   cobra.ExactArgs(1),
		RunE: func(cmd *cobra.Command, args []string) error {
			ctx := cmd.Context()

			ctx, stop := signal.NotifyContext(ctx, InterruptSignals...)
			defer stop()

			user, host, err := gitauth.ParseAskpass(args[0])
			if err != nil {
				return xerrors.Errorf("parse host: %w", err)
			}

			client, err := createAgentClient(cmd)
			if err != nil {
				return xerrors.Errorf("create agent client: %w", err)
			}

			token, err := client.WorkspaceAgentGitAuth(ctx, host, false)
			if err != nil {
				var apiError *codersdk.Error
				if errors.As(err, &apiError) && apiError.StatusCode() == http.StatusNotFound {
					// This prevents the "Run 'coder --help' for usage"
					// message from occurring.
					cmd.Printf("%s\n", apiError.Message)
					return cliui.Canceled
				}
				return xerrors.Errorf("get git token: %w", err)
			}
			if token.URL != "" {
				if err := openURL(cmd, token.URL); err == nil {
					cmd.Printf("Your browser has been opened to authenticate with Git:\n\n\t%s\n\n", token.URL)
				} else {
					cmd.Printf("Open the following URL to authenticate with Git:\n\n\t%s\n\n", token.URL)
				}

				for r := retry.New(250*time.Millisecond, 10*time.Second); r.Wait(ctx); {
					token, err = client.WorkspaceAgentGitAuth(ctx, host, true)
					if err != nil {
						continue
					}
					cmd.Printf("You've been authenticated with Git!\n")
					break
				}
			}

			if token.Password != "" {
				if user == "" {
					fmt.Fprintln(cmd.OutOrStdout(), token.Username)
				} else {
					fmt.Fprintln(cmd.OutOrStdout(), token.Password)
				}
			} else {
				fmt.Fprintln(cmd.OutOrStdout(), token.Username)
			}

			return nil
		},
	}
}
