//go:build !integration

package note

import (
	"fmt"
	"net/http"
	"testing"

	"github.com/spf13/cobra"
	"github.com/stretchr/testify/assert"
	"github.com/survivorbat/huhtest"

	"gitlab.com/gitlab-org/cli/internal/cmdutils"
	"gitlab.com/gitlab-org/cli/internal/commands/issuable"
	"gitlab.com/gitlab-org/cli/internal/config"
	"gitlab.com/gitlab-org/cli/internal/glinstance"
	"gitlab.com/gitlab-org/cli/internal/testing/cmdtest"
	"gitlab.com/gitlab-org/cli/internal/testing/httpmock"
	"gitlab.com/gitlab-org/cli/test"
)

func runCommand(t *testing.T, rt http.RoundTripper, cli string, issueType issuable.IssueType) (*test.CmdOut, error) {
	t.Helper()

	ios, _, stdout, stderr := cmdtest.TestIOStreams(cmdtest.WithTestIOStreamsAsTTY(true))
	factory := cmdtest.NewTestFactory(ios,
		cmdtest.WithGitLabClient(cmdtest.NewTestApiClient(t, &http.Client{Transport: rt}, "", glinstance.DefaultHostname).Lab()),
		cmdtest.WithConfig(config.NewFromString("editor: vi")),
	)

	cmd := NewCmdNote(factory, issueType)

	return cmdtest.ExecuteCommand(cmd, cli, stdout, stderr)
}

func Test_NewCmdNote(t *testing.T) {
	fakeHTTP := httpmock.New()
	defer fakeHTTP.Verify(t)

	commands := []struct {
		name      string
		issueType issuable.IssueType
	}{
		{"issue", issuable.TypeIssue},
		{"incident", issuable.TypeIncident},
	}

	for _, cc := range commands {
		t.Run("--message flag specified", func(t *testing.T) {
			fakeHTTP.RegisterResponder(http.MethodPost, "/projects/OWNER/REPO/issues/1/notes",
				httpmock.NewStringResponse(http.StatusCreated, `
				{
					"id": 301,
					"created_at": "2013-10-02T08:57:14Z",
					"updated_at": "2013-10-02T08:57:14Z",
					"system": false,
					"noteable_id": 1,
					"noteable_type": "MergeRequest",
					"noteable_iid": 1
				}
			`))

			fakeHTTP.RegisterResponder(http.MethodGet, "/projects/OWNER/REPO/issues/1",
				httpmock.NewStringResponse(http.StatusOK, fmt.Sprintf(`
				{
					"id": 1,
					"iid": 1,
					"issue_type": "%s",
					"web_url": "https://gitlab.com/OWNER/REPO/issues/1"
				}
			`, cc.issueType)))

			// glab issue note 1 --message "Here is my note"
			// glab incident note 1 --message "Here is my note"
			output, err := runCommand(t, fakeHTTP, `1 --message "Here is my note"`, cc.issueType)
			if err != nil {
				t.Error(err)
				return
			}
			assert.Equal(t, output.Stderr(), "")
			assert.Equal(t, output.String(), "https://gitlab.com/OWNER/REPO/issues/1#note_301\n")
		})

		t.Run("issue not found", func(t *testing.T) {
			fakeHTTP.RegisterResponder(http.MethodGet, "/projects/OWNER/REPO/issues/122",
				httpmock.NewStringResponse(http.StatusNotFound, `
				{
					"message": "issue not found"
				}
			`))

			// glab issue note 1 --message "Here is my note"
			// glab incident note 1 --message "Here is my note"
			_, err := runCommand(t, fakeHTTP, `122`, cc.issueType)
			assert.NotNil(t, err)
			assert.Equal(t, "404 Not Found", err.Error())
		})
	}
}

func Test_NewCmdNote_error(t *testing.T) {
	fakeHTTP := httpmock.New()
	defer fakeHTTP.Verify(t)

	commands := []struct {
		name      string
		issueType issuable.IssueType
	}{
		{"issue", issuable.TypeIssue},
		{"incident", issuable.TypeIncident},
	}

	for _, cc := range commands {
		t.Run("note could not be created", func(t *testing.T) {
			fakeHTTP.RegisterResponder(http.MethodPost, "/projects/OWNER/REPO/issues/1/notes",
				httpmock.NewStringResponse(http.StatusUnauthorized, `
				{
					"message": "Unauthorized"
				}
			`))

			fakeHTTP.RegisterResponder(http.MethodGet, "/projects/OWNER/REPO/issues/1",
				httpmock.NewStringResponse(http.StatusOK, fmt.Sprintf(`
				{
					"id": 1,
					"iid": 1,
					"issue_type": "%s",
					"web_url": "https://gitlab.com/OWNER/REPO/issues/1"
				}
			`, cc.issueType)))

			// glab issue note 1 --message "Here is my note"
			// glab incident note 1 --message "Here is my note"
			_, err := runCommand(t, fakeHTTP, `1 -m "Some message"`, cc.issueType)
			assert.NotNil(t, err)
			assert.Equal(t, "POST https://gitlab.com/api/v4/projects/OWNER%2FREPO/issues/1/notes: 401 {message: Unauthorized}", err.Error())
		})
	}

	t.Run("using incident note command with issue ID", func(t *testing.T) {
		fakeHTTP.RegisterResponder(http.MethodGet, "/projects/OWNER/REPO/issues/1",
			httpmock.NewStringResponse(http.StatusOK, `
				{
					"id": 1,
					"iid": 1,
					"issue_type": "issue",
					"web_url": "https://gitlab.com/OWNER/REPO/issues/1"
				}
			`))

		output, err := runCommand(t, fakeHTTP, `1 -m "Some message"`, issuable.TypeIncident)
		assert.Nil(t, err)
		assert.Equal(t, "Incident not found, but an issue with the provided ID exists. Run `glab issue comment <id>` to comment.\n", output.String())
	})
}

func Test_IssuableNoteCreate_prompt(t *testing.T) {
	fakeHTTP := httpmock.New()
	defer fakeHTTP.Verify(t)

	commands := []struct {
		name      string
		issueType issuable.IssueType
	}{
		{"issue", issuable.TypeIssue},
		{"incident", issuable.TypeIncident},
	}

	for _, cc := range commands {
		t.Run("message provided", func(t *testing.T) {
			fakeHTTP.RegisterResponder(http.MethodPost, "/projects/OWNER/REPO/issues/1/notes",
				httpmock.NewStringResponse(http.StatusCreated, `
				{
					"id": 301,
					"created_at": "2013-10-02T08:57:14Z",
					"updated_at": "2013-10-02T08:57:14Z",
					"system": false,
					"noteable_id": 1,
					"noteable_type": "MergeRequest",
					"noteable_iid": 1
				}
			`))

			fakeHTTP.RegisterResponder(http.MethodGet, "/projects/OWNER/REPO/issues/1",
				httpmock.NewStringResponse(http.StatusOK, fmt.Sprintf(`
				{
					"id": 1,
					"iid": 1,
					"issue_type": "%s",
					"web_url": "https://gitlab.com/OWNER/REPO/issues/1"
				}
			`, cc.issueType)))

			responder := huhtest.NewResponder()
			responder.AddResponse("Message:", "some note message")

			exec := cmdtest.SetupCmdForTest(t, func(f cmdutils.Factory) *cobra.Command {
				return NewCmdNote(f, cc.issueType)
			}, false,
				cmdtest.WithGitLabClient(cmdtest.NewTestApiClient(t, &http.Client{Transport: fakeHTTP}, "", glinstance.DefaultHostname).Lab()),
				cmdtest.WithConfig(config.NewFromString("editor: vi")),
				cmdtest.WithResponder(t, responder),
			)

			// glab issue note 1
			// glab incident note 1
			output, err := exec(`1`)
			if err != nil {
				t.Error(err)
				return
			}
			assert.Equal(t, "", output.Stderr())
			assert.Contains(t, output.String(), "https://gitlab.com/OWNER/REPO/issues/1#note_301")
		})

		tests := []struct {
			name    string
			message string
		}{
			{"message is empty", ""},
			{"message contains only spaces", "   "},
			{"message contains only line breaks", "\n\n"},
		}

		for _, tt := range tests {
			t.Run(tt.name, func(t *testing.T) {
				fakeHTTP.RegisterResponder(http.MethodGet, "/projects/OWNER/REPO/issues/1",
					httpmock.NewStringResponse(http.StatusOK, fmt.Sprintf(`
					{
						"id": 1,
						"iid": 1,
						"issue_type": "%s",
						"web_url": "https://gitlab.com/OWNER/REPO/issues/1"
					}
				`, cc.issueType)))

				responder := huhtest.NewResponder()
				responder.AddResponse("Message:", tt.message)

				exec := cmdtest.SetupCmdForTest(t, func(f cmdutils.Factory) *cobra.Command {
					return NewCmdNote(f, cc.issueType)
				}, false,
					cmdtest.WithGitLabClient(cmdtest.NewTestApiClient(t, &http.Client{Transport: fakeHTTP}, "", glinstance.DefaultHostname).Lab()),
					cmdtest.WithConfig(config.NewFromString("editor: vi")),
					cmdtest.WithResponder(t, responder),
				)

				_, err := exec(`1`)
				if err == nil {
					t.Error("expected error")
					return
				}
				assert.Equal(t, "aborted... Note is empty.", err.Error())
			})
		}
	}
}
