// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0

package cli_test

import (
	"encoding/json"
	"fmt"
	"net/http"
	"strings"
	"testing"

	"github.com/absmach/magistrala/cli"
	"github.com/absmach/magistrala/internal/testsutil"
	"github.com/absmach/magistrala/pkg/errors"
	svcerr "github.com/absmach/magistrala/pkg/errors/service"
	mgsdk "github.com/absmach/magistrala/pkg/sdk"
	sdkmocks "github.com/absmach/magistrala/pkg/sdk/mocks"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/mock"
)

var (
	clientID   = testsutil.GenerateUUID(&testing.T{})
	channelID  = testsutil.GenerateUUID(&testing.T{})
	domainID   = testsutil.GenerateUUID(&testing.T{})
	profileID  = testsutil.GenerateUUID(&testing.T{})
	bootConfig = mgsdk.BootstrapConfig{
		ID:          clientID,
		Name:        "Test Bootstrap",
		ExternalID:  "09:6:0:sb:sa",
		ExternalKey: "key",
	}
	bootProfile = mgsdk.BootstrapProfile{
		ID:              profileID,
		Name:            "Test Profile",
		Description:     "Test profile",
		ContentFormat:   "go-template",
		ContentTemplate: "{\"device_id\":\"{{ .Device.ID }}\"}",
		Version:         1,
	}
	validToken   = "validToken"
	invalidToken = "invalidToken"
	extraArg     = "extra-arg"
	invalidID    = "invalidID"
	all          = "all"
)

func TestCreateBootstrapConfigCmd(t *testing.T) {
	sdkMock := new(sdkmocks.SDK)
	cli.SetSDK(sdkMock)
	bootCmd := cli.NewBootstrapCmd()
	rootCmd := setFlags(bootCmd)

	jsonConfig := fmt.Sprintf("{\"external_id\":\"09:6:0:sb:sa\", \"external_key\":\"key\", \"name\": \"%s\"}", "Test Bootstrap")
	invalidJson := fmt.Sprintf("{\"external_id\":\"09:6:0:sb:sa\", \"external_key\":\"key\", \"name\": \"%s\"", "Test Bootstrap")
	cases := []struct {
		desc          string
		args          []string
		logType       outputLog
		response      string
		sdkErr        errors.SDKError
		errLogMessage string
		id            string
	}{
		{
			desc: "create bootstrap config successfully",
			args: []string{
				jsonConfig,
				domainID,
				validToken,
			},
			logType:  createLog,
			id:       clientID,
			response: fmt.Sprintf("\ncreated: %s\n\n", clientID),
		},
		{
			desc: "create bootstrap config with invald args",
			args: []string{
				jsonConfig,
				domainID,
				validToken,
				extraArg,
			},
			logType: usageLog,
		},
		{
			desc: "create bootstrap config with invald json",
			args: []string{
				invalidJson,
				domainID,
				validToken,
			},
			sdkErr:        errors.NewSDKError(errors.New("unexpected end of JSON input")),
			errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.New("unexpected end of JSON input")),
			logType:       errLog,
		},
		{
			desc: "create bootstrap config with invald token",
			args: []string{
				jsonConfig,
				domainID,
				invalidToken,
			},
			sdkErr:        errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusUnauthorized),
			errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusUnauthorized)),
			logType:       errLog,
		},
	}

	for _, tc := range cases {
		t.Run(tc.desc, func(t *testing.T) {
			sdkCall := sdkMock.On("AddBootstrap", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.id, tc.sdkErr)
			out := executeCommand(t, rootCmd, append([]string{createCmd}, tc.args...)...)

			switch tc.logType {
			case createLog:
				assert.Equal(t, tc.response, out, fmt.Sprintf("%s unexpected error response: expected %s got errLogMessage:%s", tc.desc, tc.response, out))
			case errLog:
				assert.Equal(t, tc.errLogMessage, out, fmt.Sprintf("%s unexpected error response: expected %s got errLogMessage:%s", tc.desc, tc.errLogMessage, out))
			case usageLog:
				assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out))
			}
			sdkCall.Unset()
		})
	}
}

func TestGetBootstrapConfigCmd(t *testing.T) {
	sdkMock := new(sdkmocks.SDK)
	cli.SetSDK(sdkMock)
	bootCmd := cli.NewBootstrapCmd()
	rootCmd := setFlags(bootCmd)

	var boot mgsdk.BootstrapConfig
	var page mgsdk.BootstrapPage

	cases := []struct {
		desc          string
		args          []string
		sdkErr        errors.SDKError
		page          mgsdk.BootstrapPage
		boot          mgsdk.BootstrapConfig
		logType       outputLog
		errLogMessage string
	}{
		{
			desc: "get all bootstrap config successfully",
			args: []string{
				all,
				domainID,
				validToken,
			},
			page: mgsdk.BootstrapPage{
				PageRes: mgsdk.PageRes{
					Total:  1,
					Offset: 0,
					Limit:  10,
				},
				Configs: []mgsdk.BootstrapConfig{bootConfig},
			},
			logType: entityLog,
		},
		{
			desc: "get bootstrap config with id",
			args: []string{
				channelID,
				domainID,
				validToken,
			},
			logType: entityLog,
			boot:    bootConfig,
		},
		{
			desc: "get bootstrap config with invalid args",
			args: []string{
				all,
				domainID,
				validToken,
				extraArg,
			},
			logType: usageLog,
		},
		{
			desc: "get all bootstrap config with invalid token",
			args: []string{
				all,
				domainID,
				invalidToken,
			},
			logType:       errLog,
			sdkErr:        errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden),
			errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)),
		},
		{
			desc: "get bootstrap config with invalid id",
			args: []string{
				invalidID,
				domainID,
				validToken,
			},
			sdkErr:        errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden),
			errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)),
			logType:       errLog,
		},
	}

	for _, tc := range cases {
		t.Run(tc.desc, func(t *testing.T) {
			sdkCall := sdkMock.On("ViewBootstrap", mock.Anything, tc.args[0], tc.args[1], tc.args[2]).Return(tc.boot, tc.sdkErr)
			sdkCall1 := sdkMock.On("Bootstraps", mock.Anything, mock.Anything, tc.args[1], tc.args[2]).Return(tc.page, tc.sdkErr)

			out := executeCommand(t, rootCmd, append([]string{getCmd}, tc.args...)...)

			switch tc.logType {
			case entityLog:
				if tc.args[0] == all {
					err := json.Unmarshal([]byte(out), &page)
					assert.Nil(t, err)
					assert.Equal(t, tc.page, page, fmt.Sprintf("%v unexpected response, expected: %v, got: %v", tc.desc, tc.page, page))
				} else {
					err := json.Unmarshal([]byte(out), &boot)
					assert.Nil(t, err)
					assert.Equal(t, tc.boot, boot, fmt.Sprintf("%v unexpected response, expected: %v, got: %v", tc.desc, tc.boot, boot))
				}
			case errLog:
				assert.Equal(t, tc.errLogMessage, out, fmt.Sprintf("%s unexpected error response: expected %s got errLogMessage:%s", tc.desc, tc.errLogMessage, out))
			case usageLog:
				assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out))
			}
			sdkCall.Unset()
			sdkCall1.Unset()
		})
	}
}

func TestRemoveBootstrapConfigCmd(t *testing.T) {
	sdkMock := new(sdkmocks.SDK)
	cli.SetSDK(sdkMock)
	bootCmd := cli.NewBootstrapCmd()
	rootCmd := setFlags(bootCmd)

	cases := []struct {
		desc          string
		args          []string
		sdkErr        errors.SDKError
		logType       outputLog
		errLogMessage string
	}{
		{
			desc: "remove bootstrap config successfully",
			args: []string{
				clientID,
				domainID,
				validToken,
			},
			logType: okLog,
		},
		{
			desc: "remove bootstrap config with invalid args",
			args: []string{
				clientID,
				domainID,
				validToken,
				extraArg,
			},
			logType: usageLog,
		},
		{
			desc: "remove bootstrap config with invalid client id",
			args: []string{
				invalidID,
				domainID,
				validToken,
			},
			sdkErr:        errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden),
			errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)),
			logType:       errLog,
		},
		{
			desc: "remove bootstrap config with invalid token",
			args: []string{
				clientID,
				domainID,
				invalidToken,
			},
			sdkErr:        errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden),
			errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)),
			logType:       errLog,
		},
	}

	for _, tc := range cases {
		t.Run(tc.desc, func(t *testing.T) {
			sdkCall := sdkMock.On("RemoveBootstrap", mock.Anything, tc.args[0], tc.args[1], tc.args[2]).Return(tc.sdkErr)
			out := executeCommand(t, rootCmd, append([]string{rmCmd}, tc.args...)...)

			switch tc.logType {
			case okLog:
				assert.True(t, strings.Contains(out, "ok"), fmt.Sprintf("%s unexpected response: expected success message, got: %v", tc.desc, out))
			case errLog:
				assert.Equal(t, tc.errLogMessage, out, fmt.Sprintf("%s unexpected error response: expected %s got errLogMessage:%s", tc.desc, tc.errLogMessage, out))
			case usageLog:
				assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out))
			}
			sdkCall.Unset()
		})
	}
}

func TestUpdateBootstrapConfigCmd(t *testing.T) {
	sdkMock := new(sdkmocks.SDK)
	cli.SetSDK(sdkMock)
	bootCmd := cli.NewBootstrapCmd()
	rootCmd := setFlags(bootCmd)

	config := "config"
	connection := "connection"

	newConfigJson := "{\"name\" : \"New Bootstrap\"}"
	chanIDsJson := fmt.Sprintf("[\"%s\"]", channelID)
	cases := []struct {
		desc          string
		args          []string
		boot          mgsdk.BootstrapConfig
		sdkErr        errors.SDKError
		errLogMessage string
		logType       outputLog
	}{
		{
			desc: "update bootstrap config successfully",
			args: []string{
				config,
				newConfigJson,
				domainID,
				validToken,
			},
			logType: okLog,
		},
		{
			desc: "update bootstrap config with invalid token",
			args: []string{
				config,
				newConfigJson,
				domainID,
				invalidToken,
			},
			logType:       errLog,
			sdkErr:        errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden),
			errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)),
		},
		{
			desc: "update bootstrap connections successfully",
			args: []string{
				connection,
				clientID,
				chanIDsJson,
				domainID,
				validToken,
			},
			logType: okLog,
		},
		{
			desc: "update bootstrap connections with invalid json",
			args: []string{
				connection,
				clientID,
				fmt.Sprintf("[\"%s\"", clientID),
				domainID,
				validToken,
			},
			sdkErr:        errors.NewSDKError(errors.New("unexpected end of JSON input")),
			errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.New("unexpected end of JSON input")),
			logType:       errLog,
		},
		{
			desc: "update bootstrap connections with invalid token",
			args: []string{
				connection,
				clientID,
				chanIDsJson,
				domainID,
				invalidToken,
			},
			logType:       errLog,
			sdkErr:        errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden),
			errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)),
		},
		{
			desc: "update bootstrap certs successfully",
			args: []string{
				"certs",
				clientID,
				"client cert",
				"client key",
				"ca",
				domainID,
				validToken,
			},
			boot:    bootConfig,
			logType: entityLog,
		},
		{
			desc: "update bootstrap certs with invalid token",
			args: []string{
				"certs",
				clientID,
				"client cert",
				"client key",
				"ca",
				domainID,
				invalidToken,
			},
			logType:       errLog,
			sdkErr:        errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden),
			errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusForbidden)),
		},
		{
			desc: "update bootstrap config with invalid args",
			args: []string{
				newConfigJson,
				domainID,
				validToken,
			},
			logType: usageLog,
		},
		{
			desc: "update bootstrap config with invalid json",
			args: []string{
				config,
				"{\"name\" : \"New Bootstrap\"",
				domainID,
				validToken,
			},
			sdkErr:        errors.NewSDKError(errors.New("unexpected end of JSON input")),
			errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.New("unexpected end of JSON input")),
			logType:       errLog,
		},
		{
			desc: "update bootstrap with invalid args",
			args: []string{
				extraArg,
				extraArg,
				extraArg,
				extraArg,
				extraArg,
			},
			logType: usageLog,
		},
	}
	for _, tc := range cases {
		t.Run(tc.desc, func(t *testing.T) {
			var boot mgsdk.BootstrapConfig
			sdkCall := sdkMock.On("UpdateBootstrap", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.sdkErr)
			sdkCall1 := sdkMock.On("UpdateBootstrapConnection", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.sdkErr)
			sdkCall2 := sdkMock.On("UpdateBootstrapCerts", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.boot, tc.sdkErr)
			out := executeCommand(t, rootCmd, append([]string{updCmd}, tc.args...)...)

			switch tc.logType {
			case entityLog:
				err := json.Unmarshal([]byte(out), &boot)
				assert.Nil(t, err)
				assert.Equal(t, tc.boot, boot, fmt.Sprintf("%s unexpected response: expected: %v, got: %v", tc.desc, tc.boot, boot))
			case okLog:
				assert.True(t, strings.Contains(out, "ok"), fmt.Sprintf("%s unexpected response: expected success message, got: %v", tc.desc, out))
			case usageLog:
				assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out))
			case errLog:
				assert.Equal(t, tc.errLogMessage, out, fmt.Sprintf("%s unexpected error response: expected %s got errLogMessage:%s", tc.desc, tc.errLogMessage, out))
			}
			sdkCall.Unset()
			sdkCall1.Unset()
			sdkCall2.Unset()
		})
	}
}

func TestWhitelistConfigCmd(t *testing.T) {
	sdkMock := new(sdkmocks.SDK)
	cli.SetSDK(sdkMock)
	bootCmd := cli.NewBootstrapCmd()
	rootCmd := setFlags(bootCmd)

	jsonConfig := fmt.Sprintf("{\"client_id\": \"%s\", \"status\":%d}", clientID, 1)

	cases := []struct {
		desc          string
		args          []string
		logType       outputLog
		errLogMessage string
		sdkErr        errors.SDKError
	}{
		{
			desc: "whitelist config successfully",
			args: []string{
				jsonConfig,
				domainID,
				validToken,
			},
			logType: okLog,
		},
		{
			desc: "whitelist config with invalid args",
			args: []string{
				jsonConfig,
				domainID,
				validToken,
				extraArg,
			},
			logType: usageLog,
		},
		{
			desc: "whitelist config with invalid json",
			args: []string{
				fmt.Sprintf("{\"client_id\": \"%s\", \"status\":%d", clientID, 1),
				domainID,
				validToken,
			},
			sdkErr:        errors.NewSDKError(errors.New("unexpected end of JSON input")),
			errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.New("unexpected end of JSON input")),
			logType:       errLog,
		},
		{
			desc: "whitelist config with invalid token",
			args: []string{
				jsonConfig,
				domainID,
				invalidToken,
			},
			sdkErr:        errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusUnauthorized),
			errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusUnauthorized)),
			logType:       errLog,
		},
	}

	for _, tc := range cases {
		t.Run(tc.desc, func(t *testing.T) {
			sdkCall := sdkMock.On("Whitelist", mock.Anything, mock.Anything, mock.Anything, tc.args[1], tc.args[2]).Return(tc.sdkErr)
			out := executeCommand(t, rootCmd, append([]string{whitelistCmd}, tc.args...)...)
			switch tc.logType {
			case okLog:
				assert.True(t, strings.Contains(out, "ok"), fmt.Sprintf("%s unexpected response: expected success message, got: %v", tc.desc, out))
			case usageLog:
				assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out))
			case errLog:
				assert.Equal(t, tc.errLogMessage, out, fmt.Sprintf("%s unexpected error response: expected %s got errLogMessage:%s", tc.desc, tc.errLogMessage, out))
			}
			sdkCall.Unset()
		})
	}
}

func TestBootstrapConfigCmd(t *testing.T) {
	sdkMock := new(sdkmocks.SDK)
	cli.SetSDK(sdkMock)
	bootCmd := cli.NewBootstrapCmd()
	rootCmd := setFlags(bootCmd)

	var boot mgsdk.BootstrapConfig
	cryptoKey := "v7aT0HGxJxt2gULzr3RHwf4WIf6DusPp"
	invalidKey := "invalid key"
	cases := []struct {
		desc          string
		args          []string
		logType       outputLog
		errLogMessage string
		sdkErr        errors.SDKError
		boot          mgsdk.BootstrapConfig
	}{
		{
			desc: "bootstrap secure config successfully",
			args: []string{
				"secure",
				bootConfig.ExternalID,
				bootConfig.ExternalKey,
				cryptoKey,
			},
			boot:    bootConfig,
			logType: entityLog,
		},
		{
			desc: "bootstrap config successfully",
			args: []string{
				bootConfig.ExternalID,
				bootConfig.ExternalKey,
			},
			boot:    bootConfig,
			logType: entityLog,
		},
		{
			desc: "bootstrap secure config with invalid args",
			args: []string{
				cryptoKey,
			},

			logType: usageLog,
		},
		{
			desc: "bootstrap secure config with invalid key",
			args: []string{
				"secure",
				bootConfig.ExternalID,
				invalidKey,
				cryptoKey,
			},
			sdkErr:        errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusUnauthorized),
			errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusUnauthorized)),
			logType:       errLog,
		},
		{
			desc: "bootstrap config with invalid key",
			args: []string{
				bootConfig.ExternalID,
				invalidKey,
			},
			sdkErr:        errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusUnauthorized),
			errLogMessage: fmt.Sprintf("\nerror: %s\n\n", errors.NewSDKErrorWithStatus(svcerr.ErrAuthorization, http.StatusUnauthorized)),
			logType:       errLog,
		},
	}

	for _, tc := range cases {
		t.Run(tc.desc, func(t *testing.T) {
			sdkCall := sdkMock.On("BootstrapSecure", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.boot, tc.sdkErr)
			sdkCall1 := sdkMock.On("Bootstrap", mock.Anything, mock.Anything, mock.Anything).Return(tc.boot, tc.sdkErr)
			out := executeCommand(t, rootCmd, append([]string{bootStrapCmd}, tc.args...)...)
			switch tc.logType {
			case entityLog:
				err := json.Unmarshal([]byte(out), &boot)
				assert.Nil(t, err)
				assert.Equal(t, tc.boot, boot, fmt.Sprintf("%s unexpected response: expected: %v, got: %v", tc.desc, tc.boot, boot))
			case usageLog:
				assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out))
			case errLog:
				assert.Equal(t, tc.errLogMessage, out, fmt.Sprintf("%s unexpected error response: expected %s got errLogMessage:%s", tc.desc, tc.errLogMessage, out))
			}
			sdkCall.Unset()
			sdkCall1.Unset()
		})
	}
}

func TestBootstrapProfilesCmd(t *testing.T) {
	sdkMock := new(sdkmocks.SDK)
	cli.SetSDK(sdkMock)
	bootCmd := cli.NewBootstrapCmd()
	rootCmd := setFlags(bootCmd)

	profilePayload, err := json.Marshal(bootProfile)
	assert.Nil(t, err)
	jsonProfile := string(profilePayload)

	cases := []struct {
		desc          string
		args          []string
		profile       mgsdk.BootstrapProfile
		page          mgsdk.BootstrapProfilesPage
		sdkErr        errors.SDKError
		logType       outputLog
		errLogMessage string
	}{
		{
			desc: "create bootstrap profile successfully",
			args: []string{
				"create",
				jsonProfile,
				domainID,
				validToken,
			},
			profile: bootProfile,
			logType: entityLog,
		},
		{
			desc: "get all bootstrap profiles successfully",
			args: []string{
				"get",
				all,
				domainID,
				validToken,
			},
			page: mgsdk.BootstrapProfilesPage{
				PageRes: mgsdk.PageRes{
					Total:  1,
					Offset: 0,
					Limit:  10,
				},
				Profiles: []mgsdk.BootstrapProfile{bootProfile},
			},
			logType: entityLog,
		},
		{
			desc: "view bootstrap profile successfully",
			args: []string{
				"get",
				profileID,
				domainID,
				validToken,
			},
			profile: bootProfile,
			logType: entityLog,
		},
		{
			desc: "update bootstrap profile successfully",
			args: []string{
				"update",
				jsonProfile,
				domainID,
				validToken,
			},
			profile: bootProfile,
			logType: entityLog,
		},
		{
			desc: "remove bootstrap profile successfully",
			args: []string{
				"remove",
				profileID,
				domainID,
				validToken,
			},
			logType: okLog,
		},
	}

	for _, tc := range cases {
		t.Run(tc.desc, func(t *testing.T) {
			var gotProfile mgsdk.BootstrapProfile
			var gotPage mgsdk.BootstrapProfilesPage

			createCall := sdkMock.On("CreateBootstrapProfile", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.profile, tc.sdkErr)
			listCall := sdkMock.On("BootstrapProfiles", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.page, tc.sdkErr)
			viewCall := sdkMock.On("ViewBootstrapProfile", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.profile, tc.sdkErr)
			updateCall := sdkMock.On("UpdateBootstrapProfile", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.profile, tc.sdkErr)
			removeCall := sdkMock.On("RemoveBootstrapProfile", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.sdkErr)

			out := executeCommand(t, rootCmd, append([]string{"profiles"}, tc.args...)...)

			switch tc.logType {
			case entityLog:
				if tc.args[0] == "get" && tc.args[1] == all {
					err := json.Unmarshal([]byte(out), &gotPage)
					assert.Nil(t, err)
					assert.Equal(t, tc.page, gotPage, fmt.Sprintf("%s unexpected response: expected: %v, got: %v", tc.desc, tc.page, gotPage))
				} else {
					err := json.Unmarshal([]byte(out), &gotProfile)
					assert.Nil(t, err)
					assert.Equal(t, tc.profile, gotProfile, fmt.Sprintf("%s unexpected response: expected: %v, got: %v", tc.desc, tc.profile, gotProfile))
				}
			case okLog:
				assert.True(t, strings.Contains(out, "ok"), fmt.Sprintf("%s unexpected response: expected success message, got: %v", tc.desc, out))
			case errLog:
				assert.Equal(t, tc.errLogMessage, out, fmt.Sprintf("%s unexpected error response: expected %s got errLogMessage:%s", tc.desc, tc.errLogMessage, out))
			case usageLog:
				assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out))
			}

			createCall.Unset()
			listCall.Unset()
			viewCall.Unset()
			updateCall.Unset()
			removeCall.Unset()
		})
	}
}

func TestBootstrapEnrollmentsCmd(t *testing.T) {
	sdkMock := new(sdkmocks.SDK)
	cli.SetSDK(sdkMock)
	bootCmd := cli.NewBootstrapCmd()
	rootCmd := setFlags(bootCmd)

	bindings := []mgsdk.BootstrapBindingRequest{
		{
			Slot:       "mqtt_client",
			Type:       "client",
			ResourceID: clientID,
		},
	}
	snapshots := []mgsdk.BootstrapBindingSnapshot{
		{
			ConfigID:   clientID,
			Slot:       "mqtt_client",
			Type:       "client",
			ResourceID: clientID,
		},
	}
	jsonBindings := fmt.Sprintf("[{\"slot\":\"%s\",\"type\":\"%s\",\"resource_id\":\"%s\"}]", bindings[0].Slot, bindings[0].Type, bindings[0].ResourceID)

	cases := []struct {
		desc          string
		args          []string
		snapshots     []mgsdk.BootstrapBindingSnapshot
		sdkErr        errors.SDKError
		logType       outputLog
		errLogMessage string
	}{
		{
			desc: "assign bootstrap profile successfully",
			args: []string{
				"assign-profile",
				clientID,
				profileID,
				domainID,
				validToken,
			},
			logType: okLog,
		},
		{
			desc: "bind bootstrap resources successfully",
			args: []string{
				"bind",
				clientID,
				jsonBindings,
				domainID,
				validToken,
			},
			logType: okLog,
		},
		{
			desc: "get bootstrap bindings successfully",
			args: []string{
				"get-bindings",
				clientID,
				domainID,
				validToken,
			},
			snapshots: snapshots,
			logType:   entityLog,
		},
		{
			desc: "refresh bootstrap bindings successfully",
			args: []string{
				"refresh-bindings",
				clientID,
				domainID,
				validToken,
			},
			logType: okLog,
		},
	}

	for _, tc := range cases {
		t.Run(tc.desc, func(t *testing.T) {
			var gotSnapshots []mgsdk.BootstrapBindingSnapshot

			assignCall := sdkMock.On("AssignBootstrapProfile", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.sdkErr)
			bindCall := sdkMock.On("BindBootstrapResources", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.sdkErr)
			listCall := sdkMock.On("BootstrapBindings", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.snapshots, tc.sdkErr)
			refreshCall := sdkMock.On("RefreshBootstrapBindings", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.sdkErr)

			out := executeCommand(t, rootCmd, append([]string{"enrollments"}, tc.args...)...)

			switch tc.logType {
			case entityLog:
				err := json.Unmarshal([]byte(out), &gotSnapshots)
				assert.Nil(t, err)
				assert.Equal(t, tc.snapshots, gotSnapshots, fmt.Sprintf("%s unexpected response: expected: %v, got: %v", tc.desc, tc.snapshots, gotSnapshots))
			case okLog:
				assert.True(t, strings.Contains(out, "ok"), fmt.Sprintf("%s unexpected response: expected success message, got: %v", tc.desc, out))
			case errLog:
				assert.Equal(t, tc.errLogMessage, out, fmt.Sprintf("%s unexpected error response: expected %s got errLogMessage:%s", tc.desc, tc.errLogMessage, out))
			case usageLog:
				assert.False(t, strings.Contains(out, rootCmd.Use), fmt.Sprintf("%s invalid usage: %s", tc.desc, out))
			}

			assignCall.Unset()
			bindCall.Unset()
			listCall.Unset()
			refreshCall.Unset()
		})
	}
}
