package transfer

import (
	"bytes"
	"io"
	"net/http"
	"testing"

	"github.com/cli/cli/v2/internal/config"
	"github.com/cli/cli/v2/internal/gh"
	"github.com/cli/cli/v2/internal/ghrepo"
	"github.com/cli/cli/v2/pkg/cmdutil"
	"github.com/cli/cli/v2/pkg/httpmock"
	"github.com/cli/cli/v2/pkg/iostreams"
	"github.com/cli/cli/v2/test"
	"github.com/google/shlex"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func runCommand(rt http.RoundTripper, cli string) (*test.CmdOut, error) {
	ios, _, stdout, stderr := iostreams.Test()

	factory := &cmdutil.Factory{
		IOStreams: ios,
		HttpClient: func() (*http.Client, error) {
			return &http.Client{Transport: rt}, nil
		},
		Config: func() (gh.Config, error) {
			return config.NewBlankConfig(), nil
		},
		BaseRepo: func() (ghrepo.Interface, error) {
			return ghrepo.New("OWNER", "REPO"), nil
		},
	}

	ios.SetStdoutTTY(true)

	cmd := NewCmdTransfer(factory, nil)

	argv, err := shlex.Split(cli)
	if err != nil {
		return nil, err
	}
	cmd.SetArgs(argv)

	cmd.SetIn(&bytes.Buffer{})
	cmd.SetOut(io.Discard)
	cmd.SetErr(io.Discard)

	_, err = cmd.ExecuteC()

	return &test.CmdOut{
		OutBuf: stdout,
		ErrBuf: stderr,
	}, err
}

func TestNewCmdTransfer(t *testing.T) {
	tests := []struct {
		name         string
		cli          string
		wants        TransferOptions
		wantBaseRepo ghrepo.Interface
		wantErr      bool
	}{
		{
			name:    "no argument",
			cli:     "",
			wantErr: true,
		},
		{
			name: "issue number argument",
			cli:  "--repo cli/repo 23 OWNER/REPO",
			wants: TransferOptions{
				IssueNumber:      23,
				DestRepoSelector: "OWNER/REPO",
			},
			wantBaseRepo: ghrepo.New("cli", "repo"),
		},
		{
			name: "argument is hash prefixed number",
			// Escaping is required here to avoid what I think is shellex treating it as a comment.
			cli: "--repo cli/repo \\#23 OWNER/REPO",
			wants: TransferOptions{
				IssueNumber:      23,
				DestRepoSelector: "OWNER/REPO",
			},
			wantBaseRepo: ghrepo.New("cli", "repo"),
		},
		{
			name: "argument is a URL",
			cli:  "https://github.com/cli/cli/issues/23 OWNER/REPO",
			wants: TransferOptions{
				IssueNumber:      23,
				DestRepoSelector: "OWNER/REPO",
			},
			wantBaseRepo: ghrepo.New("cli", "cli"),
		},
		{
			name:    "argument cannot be parsed to an issue",
			cli:     "unparseable OWNER/REPO",
			wantErr: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			f := &cmdutil.Factory{}

			argv, err := shlex.Split(tt.cli)
			assert.NoError(t, err)

			var gotOpts *TransferOptions
			cmd := NewCmdTransfer(f, func(opts *TransferOptions) error {
				gotOpts = opts
				return nil
			})
			cmdutil.EnableRepoOverride(cmd, f)

			cmd.SetArgs(argv)
			cmd.SetIn(&bytes.Buffer{})
			cmd.SetOut(&bytes.Buffer{})
			cmd.SetErr(&bytes.Buffer{})

			_, cErr := cmd.ExecuteC()
			if tt.wantErr {
				require.Error(t, cErr)
				return
			}

			require.NoError(t, cErr)
			assert.Equal(t, tt.wants.IssueNumber, gotOpts.IssueNumber)
			assert.Equal(t, tt.wants.DestRepoSelector, gotOpts.DestRepoSelector)
			actualBaseRepo, err := gotOpts.BaseRepo()
			require.NoError(t, err)
			assert.True(
				t,
				ghrepo.IsSame(tt.wantBaseRepo, actualBaseRepo),
				"expected base repo %+v, got %+v", tt.wantBaseRepo, actualBaseRepo,
			)
		})
	}
}

func Test_transferRun_noflags(t *testing.T) {
	http := &httpmock.Registry{}
	defer http.Verify(t)

	output, err := runCommand(http, "")

	if err != nil {
		assert.Equal(t, "issue and destination repository are required", err.Error())
	}

	assert.Equal(t, "", output.String())
}

func Test_transferRunSuccessfulIssueTransfer(t *testing.T) {
	http := &httpmock.Registry{}
	defer http.Verify(t)

	http.Register(
		httpmock.GraphQL(`query IssueByNumber\b`),
		httpmock.StringResponse(`
			{ "data": { "repository": {
				"hasIssuesEnabled": true,
				"issue": { "id": "THE-ID", "number": 1234, "title": "The title of the issue"}
			} } }`))

	http.Register(
		httpmock.GraphQL(`query IssueRepositoryInfo\b`),
		httpmock.StringResponse(`
				{ "data": { "repository": {
						"id": "dest-id",
						"name": "REPO1",
						"owner": { "login": "OWNER1" },
						"viewerPermission": "WRITE",
						"hasIssuesEnabled": true
				}}}`))

	http.Register(
		httpmock.GraphQL(`mutation IssueTransfer\b`),
		httpmock.GraphQLMutation(`{"data":{"transferIssue":{"issue":{"url":"https://github.com/OWNER1/REPO1/issues/1"}}}}`, func(input map[string]interface{}) {
			assert.Equal(t, input["issueId"], "THE-ID")
			assert.Equal(t, input["repositoryId"], "dest-id")
		}))

	output, err := runCommand(http, "1234 OWNER1/REPO1")
	if err != nil {
		t.Errorf("error running command `issue transfer`: %v", err)
	}
	assert.Equal(t, "https://github.com/OWNER1/REPO1/issues/1\n", output.String())
}
