package secret_test

import (
	"bytes"
	"context"
	"testing"

	"github.com/golang/mock/gomock"
	"github.com/influxdata/influx-cli/v2/api"
	"github.com/influxdata/influx-cli/v2/clients"
	"github.com/influxdata/influx-cli/v2/clients/secret"
	"github.com/influxdata/influx-cli/v2/config"
	"github.com/influxdata/influx-cli/v2/internal/mock"
	"github.com/stretchr/testify/require"
)

const (
	defaultOrgName = "default org"
	fakeResults    = "data data data"
	fakeKey        = "key1"
)

func TestSecret_List(t *testing.T) {
	t.Parallel()

	printHeader := "Key\t\tOrganization ID\n"
	id := "1111111111111111"

	testCases := []struct {
		name                 string
		params               secret.ListParams
		defaultOrgName       string
		registerExpectations func(t *testing.T, secretApi *mock.MockSecretsApi, orgApi *mock.MockOrganizationsApi)
		expectMatcher        string
		expectError          string
	}{
		{
			name: "org id",
			params: secret.ListParams{
				OrgParams: clients.OrgParams{
					OrgID: id,
				},
			},
			defaultOrgName: defaultOrgName,
			registerExpectations: func(t *testing.T, secretApi *mock.MockSecretsApi, orgApi *mock.MockOrganizationsApi) {
				req := api.ApiGetOrgsIDSecretsRequest{ApiService: secretApi}.OrgID(id)
				secretApi.EXPECT().GetOrgsIDSecrets(gomock.Any(), gomock.Eq(id)).Return(req)
				secretApi.EXPECT().GetOrgsIDSecretsExecute(gomock.Eq(req)).
					Return(api.SecretKeysResponse{Secrets: &[]string{fakeResults}}, nil)
			},
			expectMatcher: printHeader + fakeResults + "\t" + id + "\n",
		},
		{
			name: "default org",
			params: secret.ListParams{
				OrgParams: clients.OrgParams{},
			},
			defaultOrgName: defaultOrgName,
			registerExpectations: func(t *testing.T, secretApi *mock.MockSecretsApi, orgApi *mock.MockOrganizationsApi) {
				orgReq := api.ApiGetOrgsRequest{ApiService: orgApi}.Org(defaultOrgName)
				orgObj := api.Organization{Name: defaultOrgName}
				orgApi.EXPECT().GetOrgs(gomock.Any()).Return(orgReq)
				orgApi.EXPECT().GetOrgsExecute(orgReq).Return(api.Organizations{Orgs: &[]api.Organization{orgObj}}, nil)

				secReq := api.ApiGetOrgsIDSecretsRequest{ApiService: secretApi}.OrgID(orgObj.GetId())
				secObj := api.SecretKeysResponse{}
				secretApi.EXPECT().GetOrgsIDSecrets(gomock.Any(), orgObj.GetId()).Return(secReq)
				secretApi.EXPECT().GetOrgsIDSecretsExecute(secReq).Return(secObj, nil)
			},
			expectMatcher: "Key\tOrganization ID\n",
		},
		{
			name: "no org provided",
			params: secret.ListParams{
				OrgParams: clients.OrgParams{},
			},
			expectError: "must specify org ID or org name",
		},
	}

	for _, tc := range testCases {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()

			ctrl := gomock.NewController(t)
			stdio := mock.NewMockStdIO(ctrl)
			writtenBytes := bytes.Buffer{}
			stdio.EXPECT().Write(gomock.Any()).DoAndReturn(writtenBytes.Write).AnyTimes()

			secretsApi := mock.NewMockSecretsApi(ctrl)
			organizationsApi := mock.NewMockOrganizationsApi(ctrl)
			if tc.registerExpectations != nil {
				tc.registerExpectations(t, secretsApi, organizationsApi)
			}
			cli := secret.Client{
				CLI: clients.CLI{
					ActiveConfig: config.Config{
						Org: tc.defaultOrgName,
					},
					StdIO: stdio,
				},
				SecretsApi:       secretsApi,
				OrganizationsApi: organizationsApi,
			}

			err := cli.List(context.Background(), &tc.params)
			if tc.expectError != "" {
				require.Error(t, err)
				require.Equal(t, tc.expectError, err.Error())
				return
			}
			require.NoError(t, err)
			require.Equal(t, tc.expectMatcher, writtenBytes.String())
		})
	}
}

func TestSecret_Delete(t *testing.T) {
	t.Parallel()

	printHeader := "Key\tOrganization ID\t\tDeleted\n"
	id := "1111111111111111"

	testCases := []struct {
		name                 string
		params               secret.DeleteParams
		defaultOrgName       string
		registerExpectations func(t *testing.T, secretApi *mock.MockSecretsApi)
		expectMatcher        string
		expectError          string
	}{
		{
			name: "delete",
			params: secret.DeleteParams{
				OrgParams: clients.OrgParams{
					OrgID: id,
				},
				Key: fakeKey,
			},
			defaultOrgName: defaultOrgName,
			registerExpectations: func(t *testing.T, secretApi *mock.MockSecretsApi) {
				req := api.ApiDeleteOrgsIDSecretsIDRequest{ApiService: secretApi}.
					OrgID(id).
					SecretID("key1")
				secretApi.EXPECT().DeleteOrgsIDSecretsID(gomock.Any(), gomock.Eq(id), gomock.Eq("key1")).Return(req)
				secretApi.EXPECT().DeleteOrgsIDSecretsIDExecute(gomock.Eq(req)).Return(nil)
			},
			expectMatcher: printHeader + fakeKey + "\t" + id + "\ttrue\n",
		},
		{
			// This situation cannot happen since the CLI will stop it.
			// Still worth testing though
			name: "delete no key",
			params: secret.DeleteParams{
				OrgParams: clients.OrgParams{
					OrgID: id,
				},
			},
			defaultOrgName: defaultOrgName,
			registerExpectations: func(t *testing.T, secretApi *mock.MockSecretsApi) {
				req := api.ApiDeleteOrgsIDSecretsIDRequest{ApiService: secretApi}.
					OrgID(id).
					SecretID("")
				secretApi.EXPECT().DeleteOrgsIDSecretsID(gomock.Any(), gomock.Eq(id), gomock.Eq("")).Return(req)
				secretApi.EXPECT().DeleteOrgsIDSecretsIDExecute(gomock.Eq(req)).Return(nil)
			},
			expectMatcher: printHeader + "\t" + id + "\ttrue\n",
		},
		{
			name: "delete no org",
			params: secret.DeleteParams{
				Key: fakeKey,
			},
			expectError: "must specify org ID or org name",
		},
	}

	for _, tc := range testCases {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()

			ctrl := gomock.NewController(t)
			stdio := mock.NewMockStdIO(ctrl)
			writtenBytes := bytes.Buffer{}
			stdio.EXPECT().Write(gomock.Any()).DoAndReturn(writtenBytes.Write).AnyTimes()

			secretsApi := mock.NewMockSecretsApi(ctrl)
			if tc.registerExpectations != nil {
				tc.registerExpectations(t, secretsApi)
			}
			cli := secret.Client{
				CLI: clients.CLI{
					ActiveConfig: config.Config{
						Org: tc.defaultOrgName,
					},
					StdIO: stdio,
				},
				SecretsApi: secretsApi,
			}

			err := cli.Delete(context.Background(), &tc.params)
			if tc.expectError != "" {
				require.Error(t, err)
				require.Equal(t, tc.expectError, err.Error())
				return
			}
			require.NoError(t, err)
			require.Equal(t, tc.expectMatcher, writtenBytes.String())
		})
	}
}

func TestSecret_Update(t *testing.T) {
	t.Parallel()

	printHeader := "Key\tOrganization ID\n"
	fakeValue := "someValue"
	id := "1111111111111111"

	testCases := []struct {
		name                 string
		params               secret.UpdateParams
		defaultOrgName       string
		registerExpectations func(t *testing.T, secretApi *mock.MockSecretsApi, stdio *mock.MockStdIO)
		expectMatcher        string
		expectError          string
	}{
		{
			name: "update",
			params: secret.UpdateParams{
				OrgParams: clients.OrgParams{
					OrgID: id,
				},
				Key:   fakeKey,
				Value: fakeValue,
			},
			defaultOrgName: defaultOrgName,
			registerExpectations: func(t *testing.T, secretApi *mock.MockSecretsApi, stdio *mock.MockStdIO) {
				req := api.ApiPatchOrgsIDSecretsRequest{ApiService: secretApi}.
					OrgID(id).
					RequestBody(map[string]string{fakeKey: fakeValue})
				secretApi.EXPECT().PatchOrgsIDSecrets(gomock.Any(), gomock.Eq(id)).Return(req)
				secretApi.EXPECT().PatchOrgsIDSecretsExecute(gomock.Eq(req)).Return(nil)
			},
			expectMatcher: printHeader + fakeKey + "\t" + id + "\n",
		},
		{
			name: "update no key",
			params: secret.UpdateParams{
				OrgParams: clients.OrgParams{
					OrgID: id,
				},
				Value: fakeValue,
			},
			defaultOrgName: defaultOrgName,
			registerExpectations: func(t *testing.T, secretApi *mock.MockSecretsApi, stdio *mock.MockStdIO) {
				req := api.ApiPatchOrgsIDSecretsRequest{ApiService: secretApi}.
					OrgID(id).
					RequestBody(map[string]string{"": fakeValue})
				secretApi.EXPECT().PatchOrgsIDSecrets(gomock.Any(), gomock.Eq(id)).Return(req)
				secretApi.EXPECT().PatchOrgsIDSecretsExecute(gomock.Eq(req)).Return(nil)
			},
			expectMatcher: printHeader + "\t" + id + "\n",
		},
		{
			name: "update no value",
			params: secret.UpdateParams{
				OrgParams: clients.OrgParams{
					OrgID: id,
				},
				Key: fakeKey,
			},
			defaultOrgName: defaultOrgName,
			registerExpectations: func(t *testing.T, secretApi *mock.MockSecretsApi, stdio *mock.MockStdIO) {
				stdio.EXPECT().GetSecret(gomock.Eq("Please type your secret"), gomock.Eq(0)).Return(fakeValue, nil)

				req := api.ApiPatchOrgsIDSecretsRequest{ApiService: secretApi}.
					OrgID(id).
					RequestBody(map[string]string{fakeKey: fakeValue})
				secretApi.EXPECT().PatchOrgsIDSecrets(gomock.Any(), gomock.Eq(id)).Return(req)
				secretApi.EXPECT().PatchOrgsIDSecretsExecute(gomock.Eq(req)).Return(nil)
			},
			expectMatcher: printHeader + fakeKey + "\t" + id + "\n",
		},
		{
			name: "update no org",
			params: secret.UpdateParams{
				Key:   fakeKey,
				Value: fakeValue,
			},
			expectError: "must specify org ID or org name",
		},
	}

	for _, tc := range testCases {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()

			ctrl := gomock.NewController(t)
			stdio := mock.NewMockStdIO(ctrl)
			writtenBytes := bytes.Buffer{}
			stdio.EXPECT().Write(gomock.Any()).DoAndReturn(writtenBytes.Write).AnyTimes()

			secretsApi := mock.NewMockSecretsApi(ctrl)
			if tc.registerExpectations != nil {
				tc.registerExpectations(t, secretsApi, stdio)
			}
			cli := secret.Client{
				CLI: clients.CLI{
					ActiveConfig: config.Config{
						Org: tc.defaultOrgName,
					},
					StdIO: stdio,
				},
				SecretsApi: secretsApi,
			}

			err := cli.Update(context.Background(), &tc.params)
			if tc.expectError != "" {
				require.Error(t, err)
				require.Equal(t, tc.expectError, err.Error())
				return
			}
			require.NoError(t, err)
			require.Equal(t, tc.expectMatcher, writtenBytes.String())
		})
	}
}
