package delete

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

	"github.com/MakeNowJust/heredoc"
	"github.com/cli/cli/v2/api"
	"github.com/cli/cli/v2/internal/gh"
	"github.com/cli/cli/v2/internal/prompter"
	"github.com/cli/cli/v2/pkg/cmd/gist/shared"
	"github.com/cli/cli/v2/pkg/cmdutil"
	"github.com/cli/cli/v2/pkg/iostreams"
	"github.com/spf13/cobra"
)

type DeleteOptions struct {
	IO         *iostreams.IOStreams
	Config     func() (gh.Config, error)
	HttpClient func() (*http.Client, error)
	Prompter   prompter.Prompter

	Selector  string
	Confirmed bool
}

func NewCmdDelete(f *cmdutil.Factory, runF func(*DeleteOptions) error) *cobra.Command {
	opts := DeleteOptions{
		IO:         f.IOStreams,
		Config:     f.Config,
		HttpClient: f.HttpClient,
		Prompter:   f.Prompter,
	}

	cmd := &cobra.Command{
		Use:   "delete {<id> | <url>}",
		Short: "Delete a gist",
		Long: heredoc.Docf(`
			Delete a GitHub gist.

			To delete a gist interactively, use %[1]sgh gist delete%[1]s with no arguments.

			To delete a gist non-interactively, supply the gist id or url.
		`, "`"),
		Example: heredoc.Doc(`
			# Delete a gist interactively
			$ gh gist delete

			# Delete a gist non-interactively
			$ gh gist delete 1234
		`),
		Args: cobra.MaximumNArgs(1),
		RunE: func(c *cobra.Command, args []string) error {
			if !opts.IO.CanPrompt() && !opts.Confirmed {
				return cmdutil.FlagErrorf("--yes required when not running interactively")
			}

			if !opts.IO.CanPrompt() && len(args) == 0 {
				return cmdutil.FlagErrorf("id or url argument required in non-interactive mode")
			}

			if len(args) == 1 {
				opts.Selector = args[0]
			}

			if runF != nil {
				return runF(&opts)
			}
			return deleteRun(&opts)
		},
	}
	cmd.Flags().BoolVar(&opts.Confirmed, "yes", false, "Confirm deletion without prompting")
	return cmd
}

func deleteRun(opts *DeleteOptions) error {
	client, err := opts.HttpClient()
	if err != nil {
		return err
	}

	cfg, err := opts.Config()
	if err != nil {
		return err
	}

	host, _ := cfg.Authentication().DefaultHost()

	gistID := opts.Selector
	if strings.Contains(gistID, "/") {
		id, err := shared.GistIDFromURL(gistID)
		if err != nil {
			return err
		}
		gistID = id
	}

	cs := opts.IO.ColorScheme()
	var gist *shared.Gist
	if gistID == "" {
		gist, err = shared.PromptGists(opts.Prompter, client, host, cs)
	} else {
		gist, err = shared.GetGist(client, host, gistID)
	}
	if err != nil {
		return err
	}
	if gist.ID == "" {
		fmt.Fprintln(opts.IO.Out, "No gists found.")
		return nil
	}

	if !opts.Confirmed {
		confirmed, err := opts.Prompter.Confirm(fmt.Sprintf("Delete %q gist?", gist.Filename()), false)
		if err != nil {
			return err
		}
		if !confirmed {
			return cmdutil.CancelError
		}
	}

	apiClient := api.NewClientFromHTTP(client)
	if err := deleteGist(apiClient, host, gist.ID); err != nil {
		if errors.Is(err, shared.NotFoundErr) {
			return fmt.Errorf("unable to delete gist %q: either the gist is not found or it is not owned by you", gist.Filename())
		}
		return err
	}

	if opts.IO.IsStdoutTTY() {
		cs := opts.IO.ColorScheme()
		fmt.Fprintf(opts.IO.Out,
			"%s Gist %q deleted\n",
			cs.SuccessIcon(),
			gist.Filename())
	}

	return nil
}

func deleteGist(apiClient *api.Client, hostname string, gistID string) error {
	path := "gists/" + gistID
	err := apiClient.REST(hostname, "DELETE", path, nil, nil)
	if err != nil {
		var httpErr api.HTTPError
		if errors.As(err, &httpErr) && httpErr.StatusCode == 404 {
			return shared.NotFoundErr
		}
		return err
	}
	return nil
}
