package service

import (
	"context"
	"errors"
	"git.itzana.me/strafesnet/go-grpc/auth"
	"git.itzana.me/strafesnet/maps-service/pkg/api"
)

var (
	// ErrMissingSessionID there is no session id
	ErrMissingSessionID = errors.New("SessionID missing")
	// ErrInvalidSession caller does not have a valid session
	ErrInvalidSession = errors.New("Session invalid")
)

// Submissions roles bitflag
type Roles int32
var (
	// Only users with this role are allowed to submit models they don't own
	RolesSubmissionCreateNotModelOwner Roles = 1<<8
	RolesMapfixCreateNotModelOwner Roles = 1<<7
	RolesSubmissionUpload Roles = 1<<6
	RolesSubmissionReview Roles = 1<<5
	RolesSubmissionRelease Roles = 1<<4
	RolesScriptWrite Roles = 1<<3
	RolesMapfixUpload Roles = 1<<2
	RolesMapfixReview Roles = 1<<1
	RolesMapDownload Roles = 1<<0
	RolesEmpty Roles = 0
)

// StrafesNET group roles
type GroupRole int32
var (
	// has ScriptWrite
	RoleQuat GroupRole = 255
	RoleItzaname GroupRole = 254
	RoleStagingDeveloper GroupRole = 240
	RolesAll Roles = ^RolesEmpty
	// has SubmissionUpload
	RoleMapAdmin GroupRole = 128
	RolesMapAdmin Roles = RolesSubmissionRelease|RolesSubmissionUpload|RolesSubmissionReview|RolesSubmissionCreateNotModelOwner|RolesMapCouncil
	// has MapfixReview
	RoleMapCouncil GroupRole = 64
	RolesMapCouncil Roles = RolesMapfixReview|RolesMapfixUpload|RolesMapfixCreateNotModelOwner|RolesMapAccess
	// access to downloading maps
	RoleMapAccess GroupRole = 32
	RolesMapAccess Roles = RolesMapDownload
)

type UserInfoHandle struct {
	// Would love to know a better way to do this
	svc       *SecurityHandler
	ctx       *context.Context
	sessionId string
}
type UserInfo struct {
	UserID    uint64
	Username  string
	AvatarURL string
}

func (usr UserInfoHandle) GetUserInfo() (userInfo UserInfo, err error) {
	session, err := usr.svc.Client.GetSessionUser(*usr.ctx, &auth.IdMessage{
		SessionID: usr.sessionId,
	})
	if err != nil {
		return userInfo, err
	}
	userInfo.UserID = session.UserID
	userInfo.Username = session.Username
	userInfo.AvatarURL = session.AvatarURL
	return userInfo, nil
}
func (usr UserInfoHandle) GetUserID() (uint64, error) {
	session, err := usr.svc.Client.GetSessionUser(*usr.ctx, &auth.IdMessage{
		SessionID: usr.sessionId,
	})
	if err != nil {
		return 0, err
	}
	return session.UserID, nil
}
func (usr UserInfoHandle) Validate() (bool, error) {
	validate, err := usr.svc.Client.ValidateSession(*usr.ctx, &auth.IdMessage{
		SessionID: usr.sessionId,
	})
	if err != nil {
		return false, err
	}
	return validate.Valid, nil
}
func (usr UserInfoHandle) IsSubmitter(submitter uint64) (bool, error) {
	userId, err := usr.GetUserID()
	if err != nil {
		return false, err
	}
	return userId == submitter, nil
}
func (usr UserInfoHandle) hasRoles(wantRoles Roles) (bool, error) {
	haveroles, err := usr.GetRoles()
	if err != nil {
		return false, err
	}

	return haveroles & wantRoles == wantRoles, nil
}
func (usr UserInfoHandle) GetRoles() (Roles, error) {
	roles, err := usr.svc.Client.GetGroupRole(*usr.ctx, &auth.IdMessage{
		SessionID: usr.sessionId,
	})

	if err != nil {
		return RolesEmpty, err
	}

	// map roles into bitflag
	rolesBitflag := RolesEmpty;
	for _, r := range roles.Roles {
		switch GroupRole(r.Rank){
		case RoleQuat, RoleItzaname, RoleStagingDeveloper:
			rolesBitflag|=RolesAll
		case RoleMapAdmin:
			rolesBitflag|=RolesMapAdmin
		case RoleMapCouncil:
			rolesBitflag|=RolesMapCouncil
		case RoleMapAccess:
			rolesBitflag|=RolesMapAccess
		}
	}
	return rolesBitflag, nil
}

// RoleThumbnail
func (usr UserInfoHandle) HasRoleMapfixCreateNotModelOwner() (bool, error) {
	return usr.hasRoles(RolesMapfixCreateNotModelOwner)
}
func (usr UserInfoHandle) HasRoleSubmissionCreateNotModelOwner() (bool, error) {
	return usr.hasRoles(RolesSubmissionCreateNotModelOwner)
}
func (usr UserInfoHandle) HasRoleMapfixUpload() (bool, error) {
	return usr.hasRoles(RolesMapfixUpload)
}
func (usr UserInfoHandle) HasRoleMapfixReview() (bool, error) {
	return usr.hasRoles(RolesMapfixReview)
}
func (usr UserInfoHandle) HasRoleMapDownload() (bool, error) {
	return usr.hasRoles(RolesMapDownload)
}
func (usr UserInfoHandle) HasRoleSubmissionRelease() (bool, error) {
	return usr.hasRoles(RolesSubmissionRelease)
}
func (usr UserInfoHandle) HasRoleSubmissionUpload() (bool, error) {
	return usr.hasRoles(RolesSubmissionUpload)
}
func (usr UserInfoHandle) HasRoleSubmissionReview() (bool, error) {
	return usr.hasRoles(RolesSubmissionReview)
}
func (usr UserInfoHandle) HasRoleScriptWrite() (bool, error) {
	return usr.hasRoles(RolesScriptWrite)
}
/// Not implemented
func (usr UserInfoHandle) HasRoleMaptest() (bool, error) {
	println("HasRoleMaptest is not implemented!")
	return false, nil
}

type SecurityHandler struct {
	Client auth.AuthServiceClient
}

func (svc SecurityHandler) HandleCookieAuth(ctx context.Context, operationName api.OperationName, t api.CookieAuth) (context.Context, error) {
	sessionId := t.GetAPIKey()
	if sessionId == "" {
		return nil, ErrMissingSessionID
	}

	newCtx := context.WithValue(ctx, "UserInfo", UserInfoHandle{
		svc:       &svc,
		ctx:       &ctx,
		sessionId: sessionId,
	})

	return newCtx, nil
}