validators.gno

package permissions

import (
	"chain/banker"
	"errors"

	"gno.land/p/gnoland/boards"
	"gno.land/p/nt/ufmt/v0"

	"gno.land/r/gnoland/boards2/v1"
	"gno.land/r/sys/users"
)

// validateOpenBoardRename validates PermissionBoardRename.
//
// Expected `args` values:
// 1. Caller address
// 2. Board ID
// 3. Current board name
// 4. New board name
func validateOpenBoardRename(_ boards.Permissions, args boards.Args) error {
	caller, ok := args[0].(address)
	if !ok {
		return errors.New("expected a valid caller address")
	}

	newName, ok := args[3].(string)
	if !ok {
		return errors.New("expected new board name to be a string")
	}

	if err := checkBoardNameIsNotAddress(newName); err != nil {
		return err
	}

	if err := checkBoardNameBelongsToAddress(caller, newName); err != nil {
		return err
	}
	return nil
}

// validateOpenMemberInvite validates PermissionMemberInvite.
//
// Expected `args` values:
// 1. Caller address
// 2. Board ID
// 3. Invites
func validateOpenMemberInvite(perms boards.Permissions, args boards.Args) error {
	caller, ok := args[0].(address)
	if !ok {
		return errors.New("expected a valid caller address")
	}

	invites, ok := args[2].([]boards2.Invite)
	if !ok {
		return errors.New("expected valid user invites")
	}

	// Make sure that only owners invite other owners
	callerIsOwner := perms.HasRole(caller, boards2.RoleOwner)
	for _, v := range invites {
		if v.Role == boards2.RoleOwner && !callerIsOwner {
			return errors.New("only owners are allowed to invite other owners")
		}
	}
	return nil
}

// validateOpenRoleChange validates PermissionRoleChange.
//
// Expected `args` values:
// 1. Caller address
// 2. Board ID
// 3. Member address
// 4. Role
func validateOpenRoleChange(perms boards.Permissions, args boards.Args) error {
	caller, ok := args[0].(address)
	if !ok {
		return errors.New("expected a valid caller address")
	}

	// Owners and Admins can change roles.
	// Admins should not be able to assign or remove the Owner role from members.
	if perms.HasRole(caller, boards2.RoleAdmin) {
		role, ok := args[3].(boards.Role)
		if !ok {
			return errors.New("expected a valid member role")
		}

		if role == boards2.RoleOwner {
			return errors.New("admins are not allowed to promote members to Owner")
		} else {
			member, ok := args[2].(address)
			if !ok {
				return errors.New("expected a valid member address")
			}

			if perms.HasRole(member, boards2.RoleOwner) {
				return errors.New("admins are not allowed to remove the Owner role")
			}
		}
	}
	return nil
}

// validateOpenThreadCreate validates PermissionThreadCreate.
//
// Expected `args` values:
// 1. Caller address
// 2. Board ID
// 3. Thread ID
// 4. Title
// 5. Body
func validateOpenThreadCreate(perms boards.Permissions, args boards.Args) error {
	caller, ok := args[0].(address)
	if !ok {
		return errors.New("expected a valid caller address")
	}

	// All board members can create threads
	if perms.HasUser(caller) {
		return nil
	}

	// Require non members to have some GNOT in their accounts
	if err := checkAccountHasAmount(caller, OpenAccountAmount); err != nil {
		return ufmt.Errorf("caller is not allowed to create threads: %s", err)
	}
	return nil
}

// validateOpenReplyCreate validates PermissionReplyCreate.
//
// Expected `args` values:
// 1. Caller address
// 2. Board ID
// 3. Thread ID
// 4. Parent ID
// 5. Reply ID
// 6. Body
func validateOpenReplyCreate(perms boards.Permissions, args boards.Args) error {
	caller, ok := args[0].(address)
	if !ok {
		return errors.New("expected a valid caller address")
	}

	// All board members can reply
	if perms.HasUser(caller) {
		return nil
	}

	// Require non members to have some GNOT in their accounts
	if err := checkAccountHasAmount(caller, OpenAccountAmount); err != nil {
		return ufmt.Errorf("caller is not allowed to comment: %s", err)
	}
	return nil
}

func checkAccountHasAmount(addr address, amount int64) error {
	bnk := banker.NewBanker(banker.BankerTypeReadonly)
	coins := bnk.GetCoins(addr)
	if coins.AmountOf("ugnot") < OpenAccountAmount {
		amount = amount / 1_000_000 // ugnot -> GNOT
		return ufmt.Errorf("account amount is lower than %d GNOT", amount)
	}
	return nil
}

func checkBoardNameIsNotAddress(s string) error {
	if address(s).IsValid() {
		return errors.New("addresses are not allowed as board name")
	}
	return nil
}

func checkBoardNameBelongsToAddress(owner address, name string) error {
	// When the board name is the name of a registered user
	// check that caller is the owner of the name.
	user, _ := users.ResolveName(name)
	if user != nil && user.Addr() != owner {
		return errors.New("board name is a user name registered to a different user")
	}
	return nil
}