package identity

import (
	"fmt"
	"strconv"

	authnlib "github.com/grafana/authlib/authn"
	claims "github.com/grafana/authlib/types"
)

var _ Requester = (*StaticRequester)(nil)

// StaticRequester allows creating requester values explicitly.
// It is helpful in tests!
// This is mostly copied from:
// https://github.com/grafana/grafana/blob/v11.0.0/pkg/services/user/identity.go#L16
type StaticRequester struct {
	Type            claims.IdentityType
	UserID          int64
	UserUID         string
	OrgID           int64
	OrgName         string
	OrgRole         RoleType
	Login           string
	Name            string
	Email           string
	EmailVerified   bool
	AuthID          string
	AuthenticatedBy string
	Namespace       string
	IsGrafanaAdmin  bool
	// Permissions grouped by orgID and actions
	Permissions       map[int64]map[string][]string
	IDToken           string
	IDTokenClaims     *authnlib.Claims[authnlib.IDTokenClaims]
	AccessTokenClaims *authnlib.Claims[authnlib.AccessTokenClaims]
	CacheKey          string
}

// GetID returns typed id for the entity
func (u *StaticRequester) GetID() string {
	return claims.NewTypeID(u.Type, strconv.FormatInt(u.UserID, 10))
}

func (u *StaticRequester) GetUID() string {
	return claims.NewTypeID(u.Type, u.UserUID)
}

func (u *StaticRequester) GetIdentifier() string {
	return u.UserUID
}

func (u *StaticRequester) GetIdentityType() claims.IdentityType {
	return u.Type
}

func (u *StaticRequester) GetSubject() string {
	return claims.NewTypeID(u.Type, strconv.FormatInt(u.UserID, 10))
}

func (u *StaticRequester) GetAudience() []string {
	return []string{fmt.Sprintf("org:%d", u.OrgID)}
}

func (u *StaticRequester) GetTokenPermissions() []string {
	if u.AccessTokenClaims != nil {
		return u.AccessTokenClaims.Rest.Permissions
	}
	return []string{}
}

func (u *StaticRequester) GetTokenDelegatedPermissions() []string {
	if u.AccessTokenClaims != nil {
		return u.AccessTokenClaims.Rest.DelegatedPermissions
	}
	return []string{}
}

func (u *StaticRequester) GetEmail() string {
	return u.Email
}

func (u *StaticRequester) GetEmailVerified() bool {
	return u.EmailVerified
}

func (u *StaticRequester) GetUsername() string {
	return u.Login
}

func (u *StaticRequester) GetRawIdentifier() string {
	return u.UserUID
}

func (u *StaticRequester) GetInternalID() (int64, error) {
	return u.UserID, nil
}

func (u *StaticRequester) IsIdentityType(expected ...claims.IdentityType) bool {
	return claims.IsIdentityType(u.GetIdentityType(), expected...)
}

func (u *StaticRequester) GetExtra() map[string][]string {
	if u.IDToken != "" {
		return map[string][]string{"id-token": {u.IDToken}}
	}

	result := map[string][]string{}
	if u.AccessTokenClaims != nil && u.AccessTokenClaims.Rest.ServiceIdentity != "" {
		result[authnlib.ServiceIdentityKey] = []string{u.AccessTokenClaims.Rest.ServiceIdentity}
	}
	return result
}

func (u *StaticRequester) GetGroups() []string {
	return []string{}
}

func (u *StaticRequester) GetName() string {
	if u.Name != "" {
		return u.Name
	}
	if u.Login != "" {
		return u.Login
	}
	return u.Email
}

func (u *StaticRequester) HasRole(role RoleType) bool {
	if u.IsGrafanaAdmin {
		return true
	}

	return u.OrgRole.Includes(role)
}

// GetIsGrafanaAdmin returns true if the user is a server admin
func (u *StaticRequester) GetIsGrafanaAdmin() bool {
	return u.IsGrafanaAdmin
}

// GetLogin returns the login of the active entity
// Can be empty if the user is anonymous
func (u *StaticRequester) GetLogin() string {
	return u.Login
}

// GetOrgID returns the ID of the active organization
func (u *StaticRequester) GetOrgID() int64 {
	return u.OrgID
}

// DEPRECATED: GetOrgName returns the name of the active organization
// Retrieve the organization name from the organization service instead of using this method.
func (u *StaticRequester) GetOrgName() string {
	return u.OrgName
}

// GetPermissions returns the permissions of the active entity
func (u *StaticRequester) GetPermissions() map[string][]string {
	if u.Permissions == nil {
		return make(map[string][]string)
	}

	if u.Permissions[u.GetOrgID()] == nil {
		return make(map[string][]string)
	}

	return u.Permissions[u.GetOrgID()]
}

// GetGlobalPermissions returns the permissions of the active entity that are available across all organizations
func (u *StaticRequester) GetGlobalPermissions() map[string][]string {
	if u.Permissions == nil {
		return make(map[string][]string)
	}

	const globalOrgID = 0

	if u.Permissions[globalOrgID] == nil {
		return make(map[string][]string)
	}

	return u.Permissions[globalOrgID]
}

// DEPRECATED: GetTeams returns the teams the entity is a member of
// Retrieve the teams from the team service instead of using this method.
func (u *StaticRequester) GetTeams() []int64 {
	return []int64{} // Not implemented
}

// GetOrgRole returns the role of the active entity in the active organization
func (u *StaticRequester) GetOrgRole() RoleType {
	return u.OrgRole
}

// HasUniqueId returns true if the entity has a unique id
func (u *StaticRequester) HasUniqueId() bool {
	return u.UserID > 0
}

func (u *StaticRequester) GetAuthID() string {
	return u.AuthID
}

func (u *StaticRequester) GetNamespace() string {
	return u.Namespace
}

func (u *StaticRequester) GetAuthenticatedBy() string {
	return u.AuthenticatedBy
}

func (u *StaticRequester) IsAuthenticatedBy(providers ...string) bool {
	for _, p := range providers {
		if u.AuthenticatedBy == p {
			return true
		}
	}
	return false
}

// FIXME: remove this method once all services are using an interface
func (u *StaticRequester) IsNil() bool {
	return u == nil
}

func (u *StaticRequester) IsEmailVerified() bool {
	return u.EmailVerified
}

func (u *StaticRequester) GetCacheKey() string {
	return u.CacheKey
}

func (u *StaticRequester) GetIDToken() string {
	return u.IDToken
}
