package mock

import (
	"context"
	"errors"

	"github.com/grafana/grafana/pkg/apimachinery/identity"
	"github.com/grafana/grafana/pkg/infra/log"
	"github.com/grafana/grafana/pkg/plugins"
	"github.com/grafana/grafana/pkg/services/accesscontrol"
	"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginaccesscontrol"
	"github.com/grafana/grafana/pkg/services/user"
)

type fullAccessControl interface {
	accesscontrol.AccessControl
	accesscontrol.Service
	pluginaccesscontrol.RoleRegistry
	RegisterFixedRoles(context.Context) error
}

type Calls struct {
	Evaluate                       []interface{}
	GetRoleByName                  []interface{}
	GetUserPermissions             []interface{}
	ClearUserPermissionCache       []interface{}
	DeclareFixedRoles              []interface{}
	DeclarePluginRoles             []interface{}
	GetUserBuiltInRoles            []interface{}
	RegisterFixedRoles             []interface{}
	RegisterAttributeScopeResolver []interface{}
	DeleteUserPermissions          []interface{}
	DeleteTeamPermissions          []interface{}
	SearchUsersPermissions         []interface{}
	SearchUserPermissions          []interface{}
	SaveExternalServiceRole        []interface{}
	DeleteExternalServiceRole      []interface{}
}

type Mock struct {
	accesscontrol.Service
	accesscontrol.AccessControl

	// Unless an override is provided, permissions will be returned by GetUserPermissions
	permissions []accesscontrol.Permission
	// Unless an override is provided, builtInRoles will be returned by GetUserBuiltInRoles
	builtInRoles []string

	// Track the list of calls
	Calls Calls

	// Override functions
	EvaluateFunc                       func(context.Context, identity.Requester, accesscontrol.Evaluator) (bool, error)
	GetRoleByNameFunc                  func(context.Context, int64, string) (*accesscontrol.RoleDTO, error)
	GetUserPermissionsFunc             func(context.Context, identity.Requester, accesscontrol.Options) ([]accesscontrol.Permission, error)
	ClearUserPermissionCacheFunc       func(identity.Requester)
	DeclareFixedRolesFunc              func(...accesscontrol.RoleRegistration) error
	DeclarePluginRolesFunc             func(context.Context, string, string, []plugins.RoleRegistration) error
	GetUserBuiltInRolesFunc            func(user identity.Requester) []string
	RegisterFixedRolesFunc             func() error
	RegisterScopeAttributeResolverFunc func(string, accesscontrol.ScopeAttributeResolver)
	DeleteUserPermissionsFunc          func(context.Context, int64) error
	DeleteTeamPermissionsFunc          func(context.Context, int64) error
	SearchUsersPermissionsFunc         func(context.Context, identity.Requester, int64, accesscontrol.SearchOptions) (map[int64][]accesscontrol.Permission, error)
	SearchUserPermissionsFunc          func(ctx context.Context, orgID int64, searchOptions accesscontrol.SearchOptions) ([]accesscontrol.Permission, error)
	SaveExternalServiceRoleFunc        func(ctx context.Context, cmd accesscontrol.SaveExternalServiceRoleCommand) error
	DeleteExternalServiceRoleFunc      func(ctx context.Context, externalServiceID string) error
	SyncUserRolesFunc                  func(ctx context.Context, orgID int64, cmd accesscontrol.SyncUserRolesCommand) error

	scopeResolvers accesscontrol.Resolvers
}

// Ensure the mock stays in line with the interface
var _ fullAccessControl = New()

// Deprecated: use fake service and real access control evaluator instead
func New() *Mock {
	mock := &Mock{
		Calls:          Calls{},
		permissions:    []accesscontrol.Permission{},
		builtInRoles:   []string{},
		scopeResolvers: accesscontrol.NewResolvers(log.NewNopLogger()),
	}

	return mock
}

func (m *Mock) GetRoleByName(ctx context.Context, orgID int64, roleName string) (*accesscontrol.RoleDTO, error) {
	m.Calls.GetRoleByName = append(m.Calls.GetRoleByName, []interface{}{ctx, orgID, roleName})
	if m.GetRoleByNameFunc != nil {
		return m.GetRoleByNameFunc(ctx, orgID, roleName)
	}
	return nil, nil
}

func (m *Mock) Run(ctx context.Context) error {
	return nil
}

func (m *Mock) GetUsageStats(ctx context.Context) map[string]interface{} {
	return make(map[string]interface{})
}

func (m *Mock) WithPermissions(permissions []accesscontrol.Permission) *Mock {
	m.permissions = permissions
	return m
}

func (m *Mock) WithBuiltInRoles(builtInRoles []string) *Mock {
	m.builtInRoles = builtInRoles
	return m
}

// Evaluate evaluates access to the given resource.
// This mock uses GetUserPermissions to then call the evaluator Evaluate function.
func (m *Mock) Evaluate(ctx context.Context, usr identity.Requester, evaluator accesscontrol.Evaluator) (bool, error) {
	m.Calls.Evaluate = append(m.Calls.Evaluate, []interface{}{ctx, usr, evaluator})
	// Use override if provided
	if m.EvaluateFunc != nil {
		return m.EvaluateFunc(ctx, usr, evaluator)
	}

	var permissions map[string][]string
	permissions = usr.GetPermissions()

	if len(permissions) == 0 {
		userPermissions, err := m.GetUserPermissions(ctx, usr, accesscontrol.Options{ReloadCache: true})
		if err != nil {
			return false, err
		}
		permissions = accesscontrol.GroupScopesByActionContext(ctx, userPermissions)
	}

	if evaluator.Evaluate(permissions) {
		return true, nil
	}

	resolvedEvaluator, err := evaluator.MutateScopes(ctx, m.scopeResolvers.GetScopeAttributeMutator(usr.GetOrgID()))
	if err != nil {
		if errors.Is(err, accesscontrol.ErrResolverNotFound) {
			return false, nil
		}
		return false, err
	}

	return resolvedEvaluator.Evaluate(permissions), nil
}

// GetUserPermissions returns user permissions.
// This mock return m.permissions unless an override is provided.
func (m *Mock) GetUserPermissions(ctx context.Context, user identity.Requester, opts accesscontrol.Options) ([]accesscontrol.Permission, error) {
	m.Calls.GetUserPermissions = append(m.Calls.GetUserPermissions, []interface{}{ctx, user, opts})
	// Use override if provided
	if m.GetUserPermissionsFunc != nil {
		return m.GetUserPermissionsFunc(ctx, user, opts)
	}
	// Otherwise return the Permissions list
	return m.permissions, nil
}

func (m *Mock) ClearUserPermissionCache(user identity.Requester) {
	m.Calls.ClearUserPermissionCache = append(m.Calls.ClearUserPermissionCache, []interface{}{user})
	// Use override if provided
	if m.ClearUserPermissionCacheFunc != nil {
		m.ClearUserPermissionCacheFunc(user)
	}
}

// DeclareFixedRoles allow the caller to declare, to the service, fixed roles and their
// assignments to organization roles ("Viewer", "Editor", "Admin") or "Grafana Admin"
// This mock returns no error unless an override is provided.
func (m *Mock) DeclareFixedRoles(registrations ...accesscontrol.RoleRegistration) error {
	m.Calls.DeclareFixedRoles = append(m.Calls.DeclareFixedRoles, []interface{}{registrations})
	// Use override if provided
	if m.DeclareFixedRolesFunc != nil {
		return m.DeclareFixedRolesFunc(registrations...)
	}
	return nil
}

// RegisterFixedRoles registers all roles declared to AccessControl
// This mock returns no error unless an override is provided.
func (m *Mock) RegisterFixedRoles(ctx context.Context) error {
	m.Calls.RegisterFixedRoles = append(m.Calls.RegisterFixedRoles, []struct{}{})
	// Use override if provided
	if m.RegisterFixedRolesFunc != nil {
		return m.RegisterFixedRolesFunc()
	}
	return nil
}

// DeclarePluginRoles allow the caller to declare, to the service, plugin roles and their
// assignments to organization roles ("Viewer", "Editor", "Admin") or "Grafana Admin"
// This mock returns no error unless an override is provided.
func (m *Mock) DeclarePluginRoles(ctx context.Context, ID, name string, regs []plugins.RoleRegistration) error {
	m.Calls.DeclarePluginRoles = append(m.Calls.DeclarePluginRoles, []interface{}{ctx, ID, name, regs})
	// Use override if provided
	if m.DeclarePluginRolesFunc != nil {
		return m.DeclarePluginRolesFunc(ctx, ID, name, regs)
	}
	return nil
}

func (m *Mock) RegisterScopeAttributeResolver(scopePrefix string, resolver accesscontrol.ScopeAttributeResolver) {
	m.scopeResolvers.AddScopeAttributeResolver(scopePrefix, resolver)
	m.Calls.RegisterAttributeScopeResolver = append(m.Calls.RegisterAttributeScopeResolver, []struct{}{})
	// Use override if provided
	if m.RegisterScopeAttributeResolverFunc != nil {
		m.RegisterScopeAttributeResolverFunc(scopePrefix, resolver)
	}
}

func (m *Mock) DeleteUserPermissions(ctx context.Context, orgID, userID int64) error {
	m.Calls.DeleteUserPermissions = append(m.Calls.DeleteUserPermissions, []interface{}{ctx, orgID, userID})
	// Use override if provided
	if m.DeleteUserPermissionsFunc != nil {
		return m.DeleteUserPermissionsFunc(ctx, userID)
	}
	return nil
}

func (m *Mock) DeleteTeamPermissions(ctx context.Context, orgID, teamID int64) error {
	m.Calls.DeleteTeamPermissions = append(m.Calls.DeleteTeamPermissions, []interface{}{ctx, orgID, teamID})
	// Use override if provided
	if m.DeleteTeamPermissionsFunc != nil {
		return m.DeleteTeamPermissionsFunc(ctx, teamID)
	}
	return nil
}

// SearchUsersPermissions returns all users' permissions filtered by an action prefix
func (m *Mock) SearchUsersPermissions(ctx context.Context, usr identity.Requester, options accesscontrol.SearchOptions) (map[int64][]accesscontrol.Permission, error) {
	user := usr.(*user.SignedInUser)
	m.Calls.SearchUsersPermissions = append(m.Calls.SearchUsersPermissions, []interface{}{ctx, user, options})
	// Use override if provided
	if m.SearchUsersPermissionsFunc != nil {
		return m.SearchUsersPermissionsFunc(ctx, user, usr.GetOrgID(), options)
	}
	return nil, nil
}

func (m *Mock) SearchUserPermissions(ctx context.Context, orgID int64, searchOptions accesscontrol.SearchOptions) ([]accesscontrol.Permission, error) {
	m.Calls.SearchUserPermissions = append(m.Calls.SearchUserPermissions, []interface{}{ctx, orgID, searchOptions})
	// Use override if provided
	if m.SearchUserPermissionsFunc != nil {
		return m.SearchUserPermissionsFunc(ctx, orgID, searchOptions)
	}
	return nil, nil
}

func (m *Mock) SaveExternalServiceRole(ctx context.Context, cmd accesscontrol.SaveExternalServiceRoleCommand) error {
	m.Calls.SaveExternalServiceRole = append(m.Calls.SaveExternalServiceRole, []interface{}{ctx, cmd})
	// Use override if provided
	if m.SaveExternalServiceRoleFunc != nil {
		return m.SaveExternalServiceRoleFunc(ctx, cmd)
	}
	return nil
}

func (m *Mock) DeleteExternalServiceRole(ctx context.Context, externalServiceID string) error {
	m.Calls.DeleteExternalServiceRole = append(m.Calls.DeleteExternalServiceRole, []interface{}{ctx, externalServiceID})
	// Use override if provided
	if m.DeleteExternalServiceRoleFunc != nil {
		return m.DeleteExternalServiceRoleFunc(ctx, externalServiceID)
	}
	return nil
}

func (m *Mock) SyncUserRoles(ctx context.Context, orgID int64, cmd accesscontrol.SyncUserRolesCommand) error {
	if m.SyncUserRolesFunc != nil {
		return m.SyncUserRolesFunc(ctx, orgID, cmd)
	}
	return nil
}

// WithoutResolvers implements fullAccessControl.
func (m *Mock) WithoutResolvers() accesscontrol.AccessControl {
	return m
}
