/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package cmd

import (
	"bytes"
	"errors"
	"testing"

	"github.com/edgelesssys/constellation/v2/internal/cloud/gcpshared"
	"github.com/edgelesssys/constellation/v2/internal/constants"
	"github.com/edgelesssys/constellation/v2/internal/file"
	"github.com/edgelesssys/constellation/v2/internal/logger"
	"github.com/spf13/afero"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestIAMDestroy(t *testing.T) {
	require := require.New(t)
	someError := errors.New("failed")

	newFsExists := func() file.Handler {
		fh := file.NewHandler(afero.NewMemMapFs())
		require.NoError(fh.Write(constants.GCPServiceAccountKeyFilename, []byte("{}")))
		return fh
	}
	newFsMissing := func() file.Handler {
		fh := file.NewHandler(afero.NewMemMapFs())
		return fh
	}
	newFsWithAdminConf := func() file.Handler {
		fh := file.NewHandler(afero.NewMemMapFs())
		require.NoError(fh.Write(constants.AdminConfFilename, []byte("")))
		return fh
	}
	newFsWithStateFile := func() file.Handler {
		fh := file.NewHandler(afero.NewMemMapFs())
		require.NoError(fh.Write(constants.StateFilename, []byte("")))
		return fh
	}

	testCases := map[string]struct {
		iamDestroyer      *stubIAMDestroyer
		fh                file.Handler
		stdin             string
		yesFlag           bool
		wantErr           bool
		wantDestroyCalled bool
	}{
		"cluster running admin conf": {
			fh:           newFsWithAdminConf(),
			iamDestroyer: &stubIAMDestroyer{},
			yesFlag:      false,
			wantErr:      true,
		},
		"cluster running cluster state": {
			fh:           newFsWithStateFile(),
			iamDestroyer: &stubIAMDestroyer{},
			yesFlag:      false,
			wantErr:      true,
		},
		"file missing abort": {
			fh:           newFsMissing(),
			stdin:        "n\n",
			yesFlag:      false,
			iamDestroyer: &stubIAMDestroyer{},
		},
		"file missing": {
			fh:                newFsMissing(),
			stdin:             "y\n",
			yesFlag:           false,
			iamDestroyer:      &stubIAMDestroyer{},
			wantDestroyCalled: true,
		},
		"file exists abort": {
			fh:           newFsExists(),
			stdin:        "n\n",
			yesFlag:      false,
			iamDestroyer: &stubIAMDestroyer{},
		},
		"error destroying user": {
			fh:                newFsMissing(),
			stdin:             "y\n",
			yesFlag:           false,
			iamDestroyer:      &stubIAMDestroyer{destroyErr: someError},
			wantErr:           true,
			wantDestroyCalled: true,
		},
		"gcp delete error": {
			fh:           newFsExists(),
			yesFlag:      true,
			iamDestroyer: &stubIAMDestroyer{getTfStateKeyErr: someError},
			wantErr:      true,
		},
	}

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

			cmd := newIAMDestroyCmd()
			cmd.SetOut(&bytes.Buffer{})
			cmd.SetErr(&bytes.Buffer{})
			cmd.SetIn(bytes.NewBufferString(tc.stdin))

			c := &destroyCmd{log: logger.NewTest(t), flags: iamDestroyFlags{
				yes: tc.yesFlag,
			}}

			err := c.iamDestroy(cmd, &nopSpinner{}, tc.iamDestroyer, tc.fh)

			if tc.wantErr {
				assert.Error(err)
			} else {
				assert.NoError(err)
			}
			assert.Equal(tc.wantDestroyCalled, tc.iamDestroyer.destroyCalled)
		})
	}
}

func TestDeleteGCPServiceAccountKeyFile(t *testing.T) {
	require := require.New(t)
	someError := errors.New("failed")

	gcpFile := `
	{
		"auth_provider_x509_cert_url": "",
		"auth_uri": "",
		"client_email": "",
		"client_id": "",
		"client_x509_cert_url": "",
		"private_key": "",
		"private_key_id": "",
		"project_id": "",
		"token_uri": "",
		"type": ""
	}
	`

	newFs := func() file.Handler {
		fs := file.NewHandler(afero.NewMemMapFs())
		require.NoError(fs.Write(constants.GCPServiceAccountKeyFilename, []byte(gcpFile)))
		return fs
	}
	newFsInvalidJSON := func() file.Handler {
		fh := file.NewHandler(afero.NewMemMapFs())
		require.NoError(fh.Write(constants.GCPServiceAccountKeyFilename, []byte("asdf")))
		return fh
	}

	testCases := map[string]struct {
		destroyer          *stubIAMDestroyer
		fsHandler          file.Handler
		stdin              string
		wantErr            bool
		wantProceed        bool
		wantGetSaKeyCalled bool
	}{
		"invalid gcp json": {
			destroyer: &stubIAMDestroyer{},
			fsHandler: newFsInvalidJSON(),
			wantErr:   true,
		},
		"error getting key terraform": {
			destroyer:          &stubIAMDestroyer{getTfStateKeyErr: someError},
			fsHandler:          newFs(),
			wantErr:            true,
			wantGetSaKeyCalled: true,
		},
		"keys not same": {
			destroyer: &stubIAMDestroyer{gcpSaKey: gcpshared.ServiceAccountKey{
				Type: "somethingelse",
			}},
			fsHandler:          newFs(),
			wantGetSaKeyCalled: true,
			wantProceed:        true,
		},
		"valid": {
			destroyer:          &stubIAMDestroyer{},
			fsHandler:          newFs(),
			wantGetSaKeyCalled: true,
			wantProceed:        true,
		},
	}

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

			cmd := newIAMDestroyCmd()
			cmd.SetOut(&bytes.Buffer{})
			cmd.SetErr(&bytes.Buffer{})
			cmd.SetIn(bytes.NewBufferString(tc.stdin))

			c := &destroyCmd{log: logger.NewTest(t)}

			proceed, err := c.deleteGCPServiceAccountKeyFile(cmd, tc.destroyer, tc.fsHandler)
			if tc.wantErr {
				assert.Error(err)
			} else {
				assert.NoError(err)
			}

			assert.Equal(tc.wantProceed, proceed)
			assert.Equal(tc.wantGetSaKeyCalled, tc.destroyer.getTfStateKeyCalled)
		})
	}
}