package rename

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"strings"

	"github.com/MakeNowJust/heredoc"
	"github.com/cli/cli/v2/api"
	"github.com/cli/cli/v2/internal/config"
	"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 RenameOptions struct {
	IO         *iostreams.IOStreams
	Config     func() (config.Config, error)
	HttpClient func() (*http.Client, error)

	Selector    string
	OldFileName string
	NewFileName string
}

func NewCmdRename(f *cmdutil.Factory, runf func(*RenameOptions) error) *cobra.Command {
	opts := &RenameOptions{
		IO:         f.IOStreams,
		HttpClient: f.HttpClient,
		Config:     f.Config,
	}

	cmd := &cobra.Command{
		Use:   "rename {<id> | <url>} <oldFilename> <newFilename>",
		Short: "Rename a file in a gist",
		Long:  heredoc.Doc(`Rename a file in the given gist ID / URL.`),
		Args:  cobra.ExactArgs(3),
		RunE: func(cmd *cobra.Command, args []string) error {
			opts.Selector = args[0]
			opts.OldFileName = args[1]
			opts.NewFileName = args[2]

			if runf != nil {
				return runf(opts)
			}

			return renameRun(opts)
		},
	}

	return cmd
}

func renameRun(opts *RenameOptions) error {
	gistID := opts.Selector

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

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

	apiClient := api.NewClientFromHTTP(client)

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

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

	gist, err := shared.GetGist(client, host, gistID)
	if err != nil {
		if errors.Is(err, shared.NotFoundErr) {
			return fmt.Errorf("gist not found: %s", gistID)
		}
		return err
	}
	username, err := api.CurrentLoginName(apiClient, host)
	if err != nil {
		return err
	}

	if username != gist.Owner.Login {
		return errors.New("you do not own this gist")
	}

	_, ok := gist.Files[opts.OldFileName]
	if !ok {
		return fmt.Errorf("File %s not found in gist", opts.OldFileName)
	}

	_, ok = gist.Files[opts.NewFileName]
	if ok {
		return fmt.Errorf("File %s already exists in gist", opts.NewFileName)
	}

	gist.Files[opts.NewFileName] = gist.Files[opts.OldFileName]
	gist.Files[opts.NewFileName].Filename = opts.NewFileName
	gist.Files[opts.OldFileName] = &shared.GistFile{}

	return updateGist(apiClient, host, gist)
}

func updateGist(apiClient *api.Client, hostname string, gist *shared.Gist) error {
	body := shared.Gist{
		Description: gist.Description,
		Files:       gist.Files,
	}

	path := "gists/" + gist.ID

	requestByte, err := json.Marshal(body)
	if err != nil {
		return err
	}

	requestBody := bytes.NewReader(requestByte)

	result := shared.Gist{}

	err = apiClient.REST(hostname, "POST", path, requestBody, &result)

	if err != nil {
		return err
	}

	return nil
}
