package status

import (
	"errors"
	"fmt"
	"net/http"

	"github.com/MakeNowJust/heredoc"
	"github.com/cli/cli/v2/api"
	"github.com/cli/cli/v2/internal/config"
	"github.com/cli/cli/v2/pkg/cmd/auth/shared"
	"github.com/cli/cli/v2/pkg/cmdutil"
	"github.com/cli/cli/v2/pkg/iostreams"
	"github.com/spf13/cobra"
)

type StatusOptions struct {
	HttpClient func() (*http.Client, error)
	IO         *iostreams.IOStreams
	Config     func() (config.Config, error)

	Hostname  string
	ShowToken bool
}

func NewCmdStatus(f *cmdutil.Factory, runF func(*StatusOptions) error) *cobra.Command {
	opts := &StatusOptions{
		HttpClient: f.HttpClient,
		IO:         f.IOStreams,
		Config:     f.Config,
	}

	cmd := &cobra.Command{
		Use:   "status",
		Args:  cobra.ExactArgs(0),
		Short: "View authentication status",
		Long: heredoc.Doc(`Verifies and displays information about your authentication state.

			This command will test your authentication state for each GitHub host that gh knows about and
			report on any issues.
		`),
		RunE: func(cmd *cobra.Command, args []string) error {
			if runF != nil {
				return runF(opts)
			}

			return statusRun(opts)
		},
	}

	cmd.Flags().StringVarP(&opts.Hostname, "hostname", "h", "", "Check a specific hostname's auth status")
	cmd.Flags().BoolVarP(&opts.ShowToken, "show-token", "t", false, "Display the auth token")

	return cmd
}

func statusRun(opts *StatusOptions) error {
	cfg, err := opts.Config()
	if err != nil {
		return err
	}

	// TODO check tty

	stderr := opts.IO.ErrOut

	cs := opts.IO.ColorScheme()

	statusInfo := map[string][]string{}

	hostnames := cfg.Hosts()
	if len(hostnames) == 0 {
		fmt.Fprintf(stderr,
			"You are not logged into any GitHub hosts. Run %s to authenticate.\n", cs.Bold("gh auth login"))
		return cmdutil.SilentError
	}

	httpClient, err := opts.HttpClient()
	if err != nil {
		return err
	}

	var failed bool
	var isHostnameFound bool

	for _, hostname := range hostnames {
		if opts.Hostname != "" && opts.Hostname != hostname {
			continue
		}
		isHostnameFound = true

		token, tokenSource := cfg.AuthToken(hostname)
		_, tokenIsWriteable := shared.AuthTokenWriteable(cfg, hostname)

		statusInfo[hostname] = []string{}
		addMsg := func(x string, ys ...interface{}) {
			statusInfo[hostname] = append(statusInfo[hostname], fmt.Sprintf(x, ys...))
		}

		if err := shared.HasMinimumScopes(httpClient, hostname, token); err != nil {
			var missingScopes *shared.MissingScopesError
			if errors.As(err, &missingScopes) {
				addMsg("%s %s: the token in %s is %s", cs.Red("X"), hostname, tokenSource, err)
				if tokenIsWriteable {
					addMsg("- To request missing scopes, run: %s %s\n",
						cs.Bold("gh auth refresh -h"),
						cs.Bold(hostname))
				}
			} else {
				addMsg("%s %s: authentication failed", cs.Red("X"), hostname)
				addMsg("- The %s token in %s is no longer valid.", cs.Bold(hostname), tokenSource)
				if tokenIsWriteable {
					addMsg("- To re-authenticate, run: %s %s",
						cs.Bold("gh auth login -h"), cs.Bold(hostname))
					addMsg("- To forget about this host, run: %s %s",
						cs.Bold("gh auth logout -h"), cs.Bold(hostname))
				}
			}
			failed = true
		} else {
			apiClient := api.NewClientFromHTTP(httpClient)
			username, err := api.CurrentLoginName(apiClient, hostname)
			if err != nil {
				addMsg("%s %s: api call failed: %s", cs.Red("X"), hostname, err)
			}
			addMsg("%s Logged in to %s as %s (%s)", cs.SuccessIcon(), hostname, cs.Bold(username), tokenSource)
			proto, _ := cfg.GetOrDefault(hostname, "git_protocol")
			if proto != "" {
				addMsg("%s Git operations for %s configured to use %s protocol.",
					cs.SuccessIcon(), hostname, cs.Bold(proto))
			}
			tokenDisplay := "*******************"
			if opts.ShowToken {
				tokenDisplay = token
			}
			addMsg("%s Token: %s", cs.SuccessIcon(), tokenDisplay)
		}
		addMsg("")

		// NB we could take this opportunity to add or fix the "user" key in the hosts config. I chose
		// not to since I wanted this command to be read-only.
	}

	if !isHostnameFound {
		fmt.Fprintf(stderr,
			"Hostname %q not found among authenticated GitHub hosts\n", opts.Hostname)
		return cmdutil.SilentError
	}

	for _, hostname := range hostnames {
		lines, ok := statusInfo[hostname]
		if !ok {
			continue
		}
		fmt.Fprintf(stderr, "%s\n", cs.Bold(hostname))
		for _, line := range lines {
			fmt.Fprintf(stderr, "  %s\n", line)
		}
	}

	if failed {
		return cmdutil.SilentError
	}

	return nil
}
