package archive

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

	"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/pkg/prompt"
	"github.com/google/shlex"
	"github.com/stretchr/testify/assert"
)

func TestNewCmdArchive(t *testing.T) {
	tests := []struct {
		name    string
		input   string
		wantErr bool
		output  ArchiveOptions
		errMsg  string
	}{
		{
			name:    "no arguments no tty",
			input:   "",
			errMsg:  "--confirm required when not running interactively",
			wantErr: true,
		},
		{
			name:   "repo argument tty",
			input:  "OWNER/REPO --confirm",
			output: ArchiveOptions{RepoArg: "OWNER/REPO", Confirmed: true},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ios, _, _, _ := iostreams.Test()
			f := &cmdutil.Factory{
				IOStreams: ios,
			}
			argv, err := shlex.Split(tt.input)
			assert.NoError(t, err)
			var gotOpts *ArchiveOptions
			cmd := NewCmdArchive(f, func(opts *ArchiveOptions) error {
				gotOpts = opts
				return nil
			})
			cmd.SetArgs(argv)
			cmd.SetIn(&bytes.Buffer{})
			cmd.SetOut(&bytes.Buffer{})
			cmd.SetErr(&bytes.Buffer{})

			_, err = cmd.ExecuteC()
			if tt.wantErr {
				assert.Error(t, err)
				return
			}
			assert.NoError(t, err)
			assert.Equal(t, tt.output.RepoArg, gotOpts.RepoArg)
			assert.Equal(t, tt.output.Confirmed, gotOpts.Confirmed)
		})
	}
}

func Test_ArchiveRun(t *testing.T) {
	queryResponse := `{ "data": { "repository": { "id": "THE-ID","isArchived": %s} } }`
	tests := []struct {
		name       string
		opts       ArchiveOptions
		httpStubs  func(*httpmock.Registry)
		askStubs   func(*prompt.AskStubber)
		isTTY      bool
		wantStdout string
		wantStderr string
	}{
		{
			name:       "unarchived repo tty",
			wantStdout: "✓ Archived repository OWNER/REPO\n",
			askStubs: func(q *prompt.AskStubber) {
				//nolint:staticcheck // SA1019: q.StubOne is deprecated: use StubPrompt
				q.StubOne(true)
			},
			isTTY: true,
			opts:  ArchiveOptions{RepoArg: "OWNER/REPO"},
			httpStubs: func(reg *httpmock.Registry) {
				reg.Register(
					httpmock.GraphQL(`query RepositoryInfo\b`),
					httpmock.StringResponse(fmt.Sprintf(queryResponse, "false")))
				reg.Register(
					httpmock.GraphQL(`mutation ArchiveRepository\b`),
					httpmock.StringResponse(`{}`))
			},
		},
		{
			name:       "infer base repo",
			wantStdout: "✓ Archived repository OWNER/REPO\n",
			opts:       ArchiveOptions{},
			askStubs: func(q *prompt.AskStubber) {
				//nolint:staticcheck // SA1019: q.StubOne is deprecated: use StubPrompt
				q.StubOne(true)
			},
			isTTY: true,
			httpStubs: func(reg *httpmock.Registry) {
				reg.Register(
					httpmock.GraphQL(`query RepositoryInfo\b`),
					httpmock.StringResponse(fmt.Sprintf(queryResponse, "false")))
				reg.Register(
					httpmock.GraphQL(`mutation ArchiveRepository\b`),
					httpmock.StringResponse(`{}`))
			},
		},
		{
			name:       "archived repo tty",
			wantStderr: "! Repository OWNER/REPO is already archived\n",
			opts:       ArchiveOptions{RepoArg: "OWNER/REPO"},
			httpStubs: func(reg *httpmock.Registry) {
				reg.Register(
					httpmock.GraphQL(`query RepositoryInfo\b`),
					httpmock.StringResponse(fmt.Sprintf(queryResponse, "true")))
			},
		},
	}

	for _, tt := range tests {
		repo, _ := ghrepo.FromFullName("OWNER/REPO")
		reg := &httpmock.Registry{}
		if tt.httpStubs != nil {
			tt.httpStubs(reg)
		}

		tt.opts.BaseRepo = func() (ghrepo.Interface, error) {
			return repo, nil
		}
		tt.opts.HttpClient = func() (*http.Client, error) {
			return &http.Client{Transport: reg}, nil
		}
		ios, _, stdout, stderr := iostreams.Test()
		tt.opts.IO = ios

		//nolint:staticcheck // SA1019: prompt.InitAskStubber is deprecated: use NewAskStubber
		q, teardown := prompt.InitAskStubber()
		defer teardown()
		if tt.askStubs != nil {
			tt.askStubs(q)
		}

		t.Run(tt.name, func(t *testing.T) {
			defer reg.Verify(t)
			ios.SetStdoutTTY(tt.isTTY)
			ios.SetStderrTTY(tt.isTTY)

			err := archiveRun(&tt.opts)
			assert.NoError(t, err)
			assert.Equal(t, tt.wantStdout, stdout.String())
			assert.Equal(t, tt.wantStderr, stderr.String())
		})
	}
}
