package serviceaccounts

import (
	"context"
	"net/http"
	"strconv"

	"github.com/grafana/grafana/pkg/services/apikey"
	contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
	"github.com/grafana/grafana/pkg/web"
)

/*
ServiceAccountService is the service that manages service accounts.

Service accounts are used to authenticate API requests. They are not users and
do not have a password.
*/

// ServiceAccountRetriever is the service that retrieves service accounts.
// At the time of writing, this service is only used by the service accounts permissions service
// to avoid cyclic dependency between the ServiceAccountService and the ServiceAccountPermissionsService
type ServiceAccountRetriever interface {
	RetrieveServiceAccount(ctx context.Context, query *GetServiceAccountQuery) (*ServiceAccountProfileDTO, error)
}

//go:generate mockery --name Service --structname MockServiceAccountService --output tests --outpkg tests --filename mocks.go
type Service interface {
	ServiceAccountRetriever
	CreateServiceAccount(ctx context.Context, orgID int64, saForm *CreateServiceAccountForm) (*ServiceAccountDTO, error)
	DeleteServiceAccount(ctx context.Context, orgID, serviceAccountID int64) error
	RetrieveServiceAccountIdByName(ctx context.Context, orgID int64, name string) (int64, error)
	SearchOrgServiceAccounts(ctx context.Context, query *SearchOrgServiceAccountsQuery) (*SearchOrgServiceAccountsResult, error)
	EnableServiceAccount(ctx context.Context, orgID, serviceAccountID int64, enable bool) error
	UpdateServiceAccount(ctx context.Context, orgID, serviceAccountID int64,
		saForm *UpdateServiceAccountForm) (*ServiceAccountProfileDTO, error)

	// Tokens
	AddServiceAccountToken(ctx context.Context, serviceAccountID int64,
		cmd *AddServiceAccountTokenCommand) (*apikey.APIKey, error)
	DeleteServiceAccountToken(ctx context.Context, orgID, serviceAccountID, tokenID int64) error
	ListTokens(ctx context.Context, query *GetSATokensQuery) ([]apikey.APIKey, error)

	MigrateApiKeysToServiceAccounts(ctx context.Context, orgID int64) (*MigrationResult, error)
}

//go:generate mockery --name ExtSvcAccountsService --structname MockExtSvcAccountsService --output tests --outpkg tests --filename extsvcaccmock.go
type ExtSvcAccountsService interface {
	// EnableExtSvcAccount enables or disables the service account associated to an external service
	EnableExtSvcAccount(ctx context.Context, cmd *EnableExtSvcAccountCmd) error
	// ManageExtSvcAccount creates, updates or deletes the service account associated with an external service
	ManageExtSvcAccount(ctx context.Context, cmd *ManageExtSvcAccountCmd) (int64, error)
	// RemoveExtSvcAccount removes the external service account associated with an external service
	RemoveExtSvcAccount(ctx context.Context, orgID int64, extSvcSlug string) error
	// RetrieveExtSvcAccount fetches an external service account by ID
	RetrieveExtSvcAccount(ctx context.Context, orgID, saID int64) (*ExtSvcAccount, error)
}

func UIDToIDHandler(saService ServiceAccountRetriever) func(ctx context.Context, orgID int64, resourceID string) (string, error) {
	return func(ctx context.Context, orgID int64, resourceID string) (string, error) {
		// if saID is empty or is an integer, we assume it's a service account id and we don't need to resolve it
		_, err := strconv.ParseInt(resourceID, 10, 64)
		if resourceID == "" || err == nil {
			return resourceID, nil
		}
		serviceAccount, err := saService.RetrieveServiceAccount(ctx, &GetServiceAccountQuery{
			OrgID: orgID,
			UID:   resourceID,
		})
		if err != nil {
			return "", err
		}
		return strconv.FormatInt(serviceAccount.Id, 10), err
	}
}

func MiddlewareServiceAccountUIDResolver(saService Service, paramName string) web.Handler {
	handler := UIDToIDHandler(saService)

	return func(c *contextmodel.ReqContext) {
		// Get sa id from request, fetch service account and replace saUID with saID
		saUID := web.Params(c.Req)[paramName]
		id, err := handler(c.Req.Context(), c.GetOrgID(), saUID)
		if err == nil {
			gotParams := web.Params(c.Req)
			gotParams[paramName] = id
			web.SetURLParams(c.Req, gotParams)
		} else {
			c.JsonApiErr(http.StatusNotFound, "Not found", nil)
		}
	}
}
