package manager

import (
	"context"
	"testing"

	"github.com/stretchr/testify/require"

	"github.com/grafana/grafana/pkg/infra/db"
	"github.com/grafana/grafana/pkg/infra/log"
	"github.com/grafana/grafana/pkg/services/accesscontrol/actest"
	"github.com/grafana/grafana/pkg/services/apikey"
	"github.com/grafana/grafana/pkg/services/org"
	"github.com/grafana/grafana/pkg/services/serviceaccounts"
	"github.com/grafana/grafana/pkg/tests/testsuite"
)

type FakeServiceAccountStore struct {
	ExpectedServiceAccountID                int64
	ExpectedServiceAccountDTO               *serviceaccounts.ServiceAccountDTO
	ExpectedServiceAccountProfileDTO        *serviceaccounts.ServiceAccountProfileDTO
	ExpectedSearchServiceAccountQueryResult *serviceaccounts.SearchOrgServiceAccountsResult
	ExpectedStats                           *serviceaccounts.Stats
	expectedMigratedResults                 *serviceaccounts.MigrationResult
	ExpectedAPIKeys                         []apikey.APIKey
	ExpectedAPIKey                          *apikey.APIKey
	ExpectedBoolean                         bool
	ExpectedError                           error
}

var _ store = (*FakeServiceAccountStore)(nil)

func newServiceAccountStoreFake() *FakeServiceAccountStore {
	return &FakeServiceAccountStore{}
}

// CreateServiceAccount is a fake creating a service account.
func (f *FakeServiceAccountStore) RetrieveServiceAccount(ctx context.Context, query *serviceaccounts.GetServiceAccountQuery) (*serviceaccounts.ServiceAccountProfileDTO, error) {
	return f.ExpectedServiceAccountProfileDTO, f.ExpectedError
}

// RetrieveServiceAccountIdByName is a fake retrieving a service account id by name.
func (f *FakeServiceAccountStore) RetrieveServiceAccountIdByName(ctx context.Context, orgID int64, name string) (int64, error) {
	return f.ExpectedServiceAccountID, f.ExpectedError
}

// CreateServiceAccount is a fake creating a service account.
func (f *FakeServiceAccountStore) CreateServiceAccount(ctx context.Context, orgID int64, saForm *serviceaccounts.CreateServiceAccountForm) (*serviceaccounts.ServiceAccountDTO, error) {
	return f.ExpectedServiceAccountDTO, f.ExpectedError
}

// EnableServiceAccount implements store.
func (f *FakeServiceAccountStore) EnableServiceAccount(ctx context.Context, orgID int64, serviceAccountID int64, enable bool) error {
	return f.ExpectedError
}

// SearchOrgServiceAccounts is a fake searching for service accounts.
func (f *FakeServiceAccountStore) SearchOrgServiceAccounts(ctx context.Context, query *serviceaccounts.SearchOrgServiceAccountsQuery) (*serviceaccounts.SearchOrgServiceAccountsResult, error) {
	return f.ExpectedSearchServiceAccountQueryResult, f.ExpectedError
}

// UpdateServiceAccount is a fake updating a service account.
func (f *FakeServiceAccountStore) UpdateServiceAccount(ctx context.Context, orgID, serviceAccountID int64,
	saForm *serviceaccounts.UpdateServiceAccountForm) (*serviceaccounts.ServiceAccountProfileDTO, error) {
	return f.ExpectedServiceAccountProfileDTO, f.ExpectedError
}

// DeleteServiceAccount is a fake deleting a service account.
func (f *FakeServiceAccountStore) DeleteServiceAccount(ctx context.Context, orgID, serviceAccountID int64) error {
	return f.ExpectedError
}

// MigrateApiKeysToServiceAccounts is a fake migrating api keys to service accounts.
func (f *FakeServiceAccountStore) MigrateApiKeysToServiceAccounts(ctx context.Context, orgID int64) (*serviceaccounts.MigrationResult, error) {
	return f.expectedMigratedResults, f.ExpectedError
}

// MigrateApiKey is a fake migrating an api key to a service account.
func (f *FakeServiceAccountStore) MigrateApiKey(ctx context.Context, orgID int64, keyId int64) error {
	return f.ExpectedError
}

// RevertApiKey is a fake reverting an api key to a service account.
func (f *FakeServiceAccountStore) RevertApiKey(ctx context.Context, saId int64, keyId int64) error {
	return f.ExpectedError
}

// ListTokens is a fake listing tokens.
func (f *FakeServiceAccountStore) ListTokens(ctx context.Context, query *serviceaccounts.GetSATokensQuery) ([]apikey.APIKey, error) {
	return f.ExpectedAPIKeys, f.ExpectedError
}

// RevokeServiceAccountToken is a fake revoking a service account token.
func (f *FakeServiceAccountStore) RevokeServiceAccountToken(ctx context.Context, orgId, serviceAccountId, tokenId int64) error {
	return f.ExpectedError
}

// AddServiceAccountToken is a fake adding a service account token.
func (f *FakeServiceAccountStore) AddServiceAccountToken(ctx context.Context, serviceAccountID int64, cmd *serviceaccounts.AddServiceAccountTokenCommand) (*apikey.APIKey, error) {
	return f.ExpectedAPIKey, f.ExpectedError
}

// DeleteServiceAccountToken is a fake deleting a service account token.
func (f *FakeServiceAccountStore) DeleteServiceAccountToken(ctx context.Context, orgID, serviceAccountID, tokenID int64) error {
	return f.ExpectedError
}

// GetUsageMetrics is a fake getting usage metrics.
func (f *FakeServiceAccountStore) GetUsageMetrics(ctx context.Context) (*serviceaccounts.Stats, error) {
	return f.ExpectedStats, f.ExpectedError
}

type SecretsCheckerFake struct {
	ExpectedError error
}

func (f *SecretsCheckerFake) CheckTokens(ctx context.Context) error {
	return f.ExpectedError
}

func TestMain(m *testing.M) {
	testsuite.Run(m)
}

func TestProvideServiceAccount_DeleteServiceAccount(t *testing.T) {
	storeMock := newServiceAccountStoreFake()
	acSvc := actest.FakeService{}
	pSvc := &actest.FakePermissionsService{}
	svc := ServiceAccountsService{
		acService:         acSvc,
		permissions:       pSvc,
		store:             storeMock,
		db:                db.InitTestDB(t),
		log:               log.NewNopLogger(),
		secretScanEnabled: false,
	}
	testOrgId := 1

	t.Run("should create service account", func(t *testing.T) {
		serviceAccountName := "new Service Account"
		serviceAccountRole := org.RoleAdmin
		isDisabled := true
		saForm := &serviceaccounts.CreateServiceAccountForm{
			Name:       serviceAccountName,
			Role:       &serviceAccountRole,
			IsDisabled: &isDisabled,
		}
		storeMock.ExpectedServiceAccountDTO = &serviceaccounts.ServiceAccountDTO{
			Id:         1,
			Name:       serviceAccountName,
			Role:       string(serviceAccountRole),
			IsDisabled: isDisabled,
		}
		_, err := svc.CreateServiceAccount(context.Background(), int64(testOrgId), saForm)
		require.NoError(t, err)
	})

	t.Run("should delete service account", func(t *testing.T) {
		err := svc.DeleteServiceAccount(context.Background(), int64(testOrgId), 1)
		require.NoError(t, err)
	})
}
