package checkout

import (
	"fmt"
	"strings"

	"github.com/MakeNowJust/heredoc/v2"
	"github.com/spf13/cobra"

	gitlab "gitlab.com/gitlab-org/api/client-go"

	"gitlab.com/gitlab-org/cli/internal/api"
	"gitlab.com/gitlab-org/cli/internal/cmdutils"
	"gitlab.com/gitlab-org/cli/internal/commands/mr/mrutils"
	"gitlab.com/gitlab-org/cli/internal/git"
	"gitlab.com/gitlab-org/cli/internal/glrepo"
	"gitlab.com/gitlab-org/cli/internal/mcpannotations"
)

type mrCheckoutConfig struct {
	branch   string
	track    bool
	upstream string
}

var mrCheckoutCfg mrCheckoutConfig

func NewCmdCheckout(f cmdutils.Factory) *cobra.Command {
	mrCheckoutCmd := &cobra.Command{
		Use:   "checkout [<id> | <branch> | <url>]",
		Short: "Check out an open merge request.",
		Long:  ``,
		Example: heredoc.Doc(`
			$ glab mr checkout 1
			$ glab mr checkout branch
			$ glab mr checkout 12 --branch todo-fix
			$ glab mr checkout new-feature --set-upstream-to=upstream/main
			$ glab mr checkout https://gitlab.com/gitlab-org/cli/-/merge_requests/1234

			# Uses the checked-out branch
			$ glab mr checkout
		`),
		Args: cobra.ExactArgs(1),
		Annotations: map[string]string{
			mcpannotations.Destructive: "true",
		},
		RunE: func(cmd *cobra.Command, args []string) error {
			var err error
			var upstream string

			if mrCheckoutCfg.upstream != "" {
				upstream = mrCheckoutCfg.upstream

				if val := strings.Split(mrCheckoutCfg.upstream, "/"); len(val) > 1 {
					// Verify that we have the remote set
					repo, err := f.Remotes()
					if err != nil {
						return err
					}
					_, err = repo.FindByName(val[0])
					if err != nil {
						return err
					}
				}
			}

			client, err := f.GitLabClient()
			if err != nil {
				return err
			}

			mr, _, err := mrutils.MRFromArgs(f, args, "any")
			if err != nil {
				return err
			}

			if mrCheckoutCfg.branch == "" {
				mrCheckoutCfg.branch = mr.SourceBranch
			}

			var mrRef string
			var mrProject *gitlab.Project

			mrProject, err = api.GetProject(client, mr.SourceProjectID)
			if err != nil {
				// If we don't have access to the source project, let's try the target project
				mrProject, err = api.GetProject(client, mr.TargetProjectID)
				if err != nil {
					return err
				} else {
					// We found the target project, let's find the ref another way
					mrRef = fmt.Sprintf("refs/merge-requests/%d/head", mr.IID)
				}

			} else {
				mrRef = fmt.Sprintf("refs/heads/%s", mr.SourceBranch)
			}

			// Get the appropriate remote URL based on user's git_protocol preference
			baseRepo, err := f.BaseRepo()
			if err != nil {
				return err
			}
			cfg := f.Config()
			gitProtocol, err := cfg.Get(baseRepo.RepoHost(), "git_protocol")
			if err != nil {
				return err
			}
			repoURL := glrepo.RemoteURL(mrProject, gitProtocol)

			fetchRefSpec := fmt.Sprintf("%s:%s", mrRef, mrCheckoutCfg.branch)
			if err := git.RunCmd([]string{"fetch", repoURL, fetchRefSpec}); err != nil {
				// the remote may have diverged from local after git operations
				// try fetching without updating the branch ref before giving up
				if err := git.RunCmd([]string{"fetch", repoURL, mrRef}); err != nil {
					return err
				}
			}

			// .remote is needed for `git pull` to work
			// .pushRemote is needed for `git push` to work, if user has set `remote.pushDefault`.
			// see https://git-scm.com/docs/git-config#Documentation/git-config.txt-branchltnamegtremote
			if err := git.RunCmd([]string{"config", fmt.Sprintf("branch.%s.remote", mrCheckoutCfg.branch), repoURL}); err != nil {
				return err
			}
			if mr.AllowCollaboration {
				if err := git.RunCmd([]string{"config", fmt.Sprintf("branch.%s.pushRemote", mrCheckoutCfg.branch), repoURL}); err != nil {
					return err
				}
			}
			if err := git.RunCmd([]string{"config", fmt.Sprintf("branch.%s.merge", mrCheckoutCfg.branch), mrRef}); err != nil {
				return err
			}

			// Check out branch
			var gr git.StandardGitCommand

			if err := git.CheckoutBranch(mrCheckoutCfg.branch, gr); err != nil {
				return err
			}

			// Check out the branch
			if upstream != "" {
				if err := git.RunCmd([]string{"branch", "--set-upstream-to", upstream}); err != nil {
					return err
				}
			}
			return nil
		},
	}
	mrCheckoutCmd.Flags().StringVarP(&mrCheckoutCfg.branch, "branch", "b", "", "Check out merge request with name <branch>.")
	mrCheckoutCmd.Flags().BoolVarP(&mrCheckoutCfg.track, "track", "t", true, "Set checked out branch to track the remote branch.")
	_ = mrCheckoutCmd.Flags().MarkDeprecated("track", "Now enabled by default")
	mrCheckoutCmd.Flags().StringVarP(&mrCheckoutCfg.upstream, "set-upstream-to", "u", "", "Set tracking of checked-out branch to [REMOTE/]BRANCH.")
	return mrCheckoutCmd
}
