package native

import (
	"encoding/binary"
	"errors"
	"fmt"
	"math/big"

	"git.marketally.com/tutus-one/tutus-chain/pkg/config"
	"git.marketally.com/tutus-one/tutus-chain/pkg/core/dao"
	"git.marketally.com/tutus-one/tutus-chain/pkg/core/interop"
	"git.marketally.com/tutus-one/tutus-chain/pkg/core/interop/runtime"
	"git.marketally.com/tutus-one/tutus-chain/pkg/core/native/nativeids"
	"git.marketally.com/tutus-one/tutus-chain/pkg/core/native/nativenames"
	"git.marketally.com/tutus-one/tutus-chain/pkg/core/state"
	"git.marketally.com/tutus-one/tutus-chain/pkg/core/storage"
	"git.marketally.com/tutus-one/tutus-chain/pkg/smartcontract"
	"git.marketally.com/tutus-one/tutus-chain/pkg/smartcontract/callflag"
	"git.marketally.com/tutus-one/tutus-chain/pkg/smartcontract/manifest"
	"git.marketally.com/tutus-one/tutus-chain/pkg/util"
	"git.marketally.com/tutus-one/tutus-chain/pkg/vm/stackitem"
)

// RoleRegistry is a native contract for hierarchical role-based access control.
// It replaces NEO.CheckCommittee() with democratic governance based on Vita.
type RoleRegistry struct {
	interop.ContractMD

	// Tutus is used for fallback committee checks when TutusCommittee is not set.
	Tutus ITutus

	// tutusCommittee contains initial committee member addresses from config.
	tutusCommittee []util.Uint160
}

// RoleRegistryCache contains cached role registry data.
type RoleRegistryCache struct {
	roleCount uint64
}

// Built-in role IDs.
const (
	// RoleCommittee is the Tutus Committee role (designated officials).
	RoleCommittee uint64 = 1
	// RoleRegistrar can register new Vitas.
	RoleRegistrar uint64 = 2
	// RoleAttestor can attest identity attributes.
	RoleAttestor uint64 = 3
	// RoleOperator is a system operator (node management).
	RoleOperator uint64 = 4
)

// Storage key prefixes for RoleRegistry.
const (
	rrPrefixRole              byte = 0x01 // roleID -> Role
	rrPrefixRoleName          byte = 0x02 // name -> roleID
	rrPrefixRoleAssignment    byte = 0x03 // tokenID + roleID -> RoleAssignment
	rrPrefixTokenRoles        byte = 0x04 // tokenID -> []roleID
	rrPrefixRoleMembers       byte = 0x05 // roleID -> []tokenID
	rrPrefixPermission        byte = 0x06 // roleID + resource + action -> PermissionGrant
	rrPrefixRolePermissions   byte = 0x07 // roleID -> []permission keys
	rrPrefixCounter           byte = 0x10 // "counter" -> next roleID
	rrPrefixConfig            byte = 0x11 // "config" -> config
	rrPrefixAddressAssignment byte = 0x12 // address + roleID -> AddressRoleAssignment
	rrPrefixAddressRoles      byte = 0x13 // address -> []roleID
)

// Max lengths for validation.
const (
	maxRoleNameLength        = 64
	maxRoleDescriptionLength = 256
	maxResourceLength        = 128
	maxActionLength          = 64
)

// Errors.
var (
	ErrRoleNotFound        = errors.New("role not found")
	ErrRoleNameExists      = errors.New("role name already exists")
	ErrRoleNameTooLong     = errors.New("role name too long")
	ErrRoleDescTooLong     = errors.New("role description too long")
	ErrRoleNotActive       = errors.New("role is not active")
	ErrAssignmentNotFound  = errors.New("role assignment not found")
	ErrAssignmentExists    = errors.New("role assignment already exists")
	ErrAssignmentExpired   = errors.New("role assignment has expired")
	ErrPermissionNotFound  = errors.New("permission not found")
	ErrPermissionExists    = errors.New("permission already exists")
	ErrResourceTooLong     = errors.New("resource identifier too long")
	ErrActionTooLong       = errors.New("action identifier too long")
	ErrInvalidScope        = errors.New("invalid scope")
	ErrRoleRegistryNotCommittee = errors.New("caller is not a committee member")
	ErrBuiltinRole         = errors.New("cannot modify built-in role")
	ErrInvalidParentRole   = errors.New("invalid parent role")
	ErrCircularHierarchy   = errors.New("circular role hierarchy detected")
)

// Event names.
const (
	RoleCreatedEvent       = "RoleCreated"
	RoleDeletedEvent       = "RoleDeleted"
	RoleGrantedEvent       = "RoleGranted"
	RoleRevokedEvent       = "RoleRevoked"
	PermissionAssignedEvent = "PermissionAssigned"
	PermissionRemovedEvent  = "PermissionRemoved"
)

var (
	_ interop.Contract        = (*RoleRegistry)(nil)
	_ dao.NativeContractCache = (*RoleRegistryCache)(nil)
)

// Copy implements NativeContractCache interface.
func (c *RoleRegistryCache) Copy() dao.NativeContractCache {
	return &RoleRegistryCache{roleCount: c.roleCount}
}

// newRoleRegistry creates a new RoleRegistry contract.
func newRoleRegistry(tutusCommittee []util.Uint160) *RoleRegistry {
	r := &RoleRegistry{
		ContractMD:     *interop.NewContractMD(nativenames.RoleRegistry, nativeids.RoleRegistry),
		tutusCommittee: tutusCommittee,
	}
	defer r.BuildHFSpecificMD(r.ActiveIn())

	// Query methods
	desc := NewDescriptor("totalRoles", smartcontract.IntegerType)
	md := NewMethodAndPrice(r.totalRoles, 1<<15, callflag.ReadStates)
	r.AddMethod(md, desc)

	desc = NewDescriptor("getRole", smartcontract.ArrayType,
		manifest.NewParameter("roleID", smartcontract.IntegerType))
	md = NewMethodAndPrice(r.getRole, 1<<15, callflag.ReadStates)
	r.AddMethod(md, desc)

	desc = NewDescriptor("getRoleByName", smartcontract.ArrayType,
		manifest.NewParameter("name", smartcontract.StringType))
	md = NewMethodAndPrice(r.getRoleByName, 1<<15, callflag.ReadStates)
	r.AddMethod(md, desc)

	desc = NewDescriptor("hasRole", smartcontract.BoolType,
		manifest.NewParameter("address", smartcontract.Hash160Type),
		manifest.NewParameter("roleID", smartcontract.IntegerType))
	md = NewMethodAndPrice(r.hasRole, 1<<15, callflag.ReadStates)
	r.AddMethod(md, desc)

	desc = NewDescriptor("getRolesForAddress", smartcontract.ArrayType,
		manifest.NewParameter("address", smartcontract.Hash160Type))
	md = NewMethodAndPrice(r.getRolesForAddress, 1<<15, callflag.ReadStates)
	r.AddMethod(md, desc)

	desc = NewDescriptor("getPermissions", smartcontract.ArrayType,
		manifest.NewParameter("roleID", smartcontract.IntegerType))
	md = NewMethodAndPrice(r.getPermissions, 1<<15, callflag.ReadStates)
	r.AddMethod(md, desc)

	desc = NewDescriptor("hasPermission", smartcontract.BoolType,
		manifest.NewParameter("address", smartcontract.Hash160Type),
		manifest.NewParameter("resource", smartcontract.StringType),
		manifest.NewParameter("action", smartcontract.StringType),
		manifest.NewParameter("scope", smartcontract.IntegerType))
	md = NewMethodAndPrice(r.hasPermission, 1<<15, callflag.ReadStates)
	r.AddMethod(md, desc)

	// Admin methods (committee-only)
	desc = NewDescriptor("createRole", smartcontract.IntegerType,
		manifest.NewParameter("name", smartcontract.StringType),
		manifest.NewParameter("description", smartcontract.StringType),
		manifest.NewParameter("parentID", smartcontract.IntegerType))
	md = NewMethodAndPrice(r.createRole, 1<<15, callflag.States|callflag.AllowNotify)
	r.AddMethod(md, desc)

	desc = NewDescriptor("deleteRole", smartcontract.BoolType,
		manifest.NewParameter("roleID", smartcontract.IntegerType))
	md = NewMethodAndPrice(r.deleteRole, 1<<15, callflag.States|callflag.AllowNotify)
	r.AddMethod(md, desc)

	desc = NewDescriptor("grantRole", smartcontract.BoolType,
		manifest.NewParameter("address", smartcontract.Hash160Type),
		manifest.NewParameter("roleID", smartcontract.IntegerType),
		manifest.NewParameter("expiresAt", smartcontract.IntegerType))
	md = NewMethodAndPrice(r.grantRole, 1<<15, callflag.States|callflag.AllowNotify)
	r.AddMethod(md, desc)

	desc = NewDescriptor("revokeRole", smartcontract.BoolType,
		manifest.NewParameter("address", smartcontract.Hash160Type),
		manifest.NewParameter("roleID", smartcontract.IntegerType))
	md = NewMethodAndPrice(r.revokeRole, 1<<15, callflag.States|callflag.AllowNotify)
	r.AddMethod(md, desc)

	desc = NewDescriptor("assignPermission", smartcontract.BoolType,
		manifest.NewParameter("roleID", smartcontract.IntegerType),
		manifest.NewParameter("resource", smartcontract.StringType),
		manifest.NewParameter("action", smartcontract.StringType),
		manifest.NewParameter("scope", smartcontract.IntegerType))
	md = NewMethodAndPrice(r.assignPermission, 1<<15, callflag.States|callflag.AllowNotify)
	r.AddMethod(md, desc)

	desc = NewDescriptor("removePermission", smartcontract.BoolType,
		manifest.NewParameter("roleID", smartcontract.IntegerType),
		manifest.NewParameter("resource", smartcontract.StringType),
		manifest.NewParameter("action", smartcontract.StringType))
	md = NewMethodAndPrice(r.removePermission, 1<<15, callflag.States|callflag.AllowNotify)
	r.AddMethod(md, desc)

	// Events
	eDesc := NewEventDescriptor(RoleCreatedEvent,
		manifest.NewParameter("roleID", smartcontract.IntegerType),
		manifest.NewParameter("name", smartcontract.StringType),
		manifest.NewParameter("parentID", smartcontract.IntegerType),
		manifest.NewParameter("createdBy", smartcontract.Hash160Type))
	eMD := NewEvent(eDesc)
	r.AddEvent(eMD)

	eDesc = NewEventDescriptor(RoleDeletedEvent,
		manifest.NewParameter("roleID", smartcontract.IntegerType),
		manifest.NewParameter("deletedBy", smartcontract.Hash160Type))
	eMD = NewEvent(eDesc)
	r.AddEvent(eMD)

	eDesc = NewEventDescriptor(RoleGrantedEvent,
		manifest.NewParameter("address", smartcontract.Hash160Type),
		manifest.NewParameter("roleID", smartcontract.IntegerType),
		manifest.NewParameter("expiresAt", smartcontract.IntegerType),
		manifest.NewParameter("grantedBy", smartcontract.Hash160Type))
	eMD = NewEvent(eDesc)
	r.AddEvent(eMD)

	eDesc = NewEventDescriptor(RoleRevokedEvent,
		manifest.NewParameter("address", smartcontract.Hash160Type),
		manifest.NewParameter("roleID", smartcontract.IntegerType),
		manifest.NewParameter("revokedBy", smartcontract.Hash160Type))
	eMD = NewEvent(eDesc)
	r.AddEvent(eMD)

	eDesc = NewEventDescriptor(PermissionAssignedEvent,
		manifest.NewParameter("roleID", smartcontract.IntegerType),
		manifest.NewParameter("resource", smartcontract.StringType),
		manifest.NewParameter("action", smartcontract.StringType),
		manifest.NewParameter("scope", smartcontract.IntegerType))
	eMD = NewEvent(eDesc)
	r.AddEvent(eMD)

	eDesc = NewEventDescriptor(PermissionRemovedEvent,
		manifest.NewParameter("roleID", smartcontract.IntegerType),
		manifest.NewParameter("resource", smartcontract.StringType),
		manifest.NewParameter("action", smartcontract.StringType))
	eMD = NewEvent(eDesc)
	r.AddEvent(eMD)

	return r
}

// Initialize initializes RoleRegistry contract at genesis.
func (r *RoleRegistry) Initialize(ic *interop.Context, hf *config.Hardfork, newMD *interop.HFSpecificContractMD) error {
	if hf != r.ActiveIn() {
		return nil
	}

	cache := &RoleRegistryCache{roleCount: 0}
	ic.DAO.SetCache(r.ID, cache)

	// Initialize role counter starting at the first custom role ID (after built-ins)
	r.putRoleCounter(ic.DAO, 5) // Built-in roles are 1-4, custom roles start at 5

	// Create built-in roles
	builtinRoles := []struct {
		id          uint64
		name        string
		description string
	}{
		{RoleCommittee, "COMMITTEE", "Tutus Committee member (designated officials)"},
		{RoleRegistrar, "REGISTRAR", "Can register new Vitas"},
		{RoleAttestor, "ATTESTOR", "Can attest identity attributes"},
		{RoleOperator, "OPERATOR", "System operator (node management)"},
	}

	for _, br := range builtinRoles {
		role := &state.Role{
			ID:          br.id,
			Name:        br.name,
			Description: br.description,
			ParentID:    0,
			CreatedAt:   0,
			CreatedBy:   util.Uint160{},
			Active:      true,
		}
		err := r.putRole(ic.DAO, role)
		if err != nil {
			return fmt.Errorf("failed to create built-in role %s: %w", br.name, err)
		}
		r.putRoleNameIndex(ic.DAO, br.name, br.id)
		cache.roleCount++
	}

	// Grant COMMITTEE role to initial committee members from config
	for _, addr := range r.tutusCommittee {
		assignment := &state.AddressRoleAssignment{
			Address:   addr,
			RoleID:    RoleCommittee,
			GrantedAt: 0,
			GrantedBy: util.Uint160{},
			ExpiresAt: 0, // Never expires
			Active:    true,
		}
		err := r.putAddressRoleAssignment(ic.DAO, assignment)
		if err != nil {
			return fmt.Errorf("failed to grant COMMITTEE role to %s: %w", addr.StringLE(), err)
		}
	}

	return nil
}

// InitializeCache initializes RoleRegistry cache from DAO.
func (r *RoleRegistry) InitializeCache(_ interop.IsHardforkEnabled, blockHeight uint32, d *dao.Simple) error {
	cache := &RoleRegistryCache{}
	cache.roleCount = r.getRoleCounter(d)
	d.SetCache(r.ID, cache)
	return nil
}

// OnPersist implements the Contract interface.
func (r *RoleRegistry) OnPersist(ic *interop.Context) error {
	return nil
}

// PostPersist implements the Contract interface.
func (r *RoleRegistry) PostPersist(ic *interop.Context) error {
	return nil
}

// Metadata returns contract metadata.
func (r *RoleRegistry) Metadata() *interop.ContractMD {
	return &r.ContractMD
}

// ActiveIn implements the Contract interface.
func (r *RoleRegistry) ActiveIn() *config.Hardfork {
	return nil
}

// CheckCommittee returns true if the caller has the COMMITTEE role.
// This replaces NEO.CheckCommittee() throughout Tutus for democratic governance.
// Falls back to NEO.CheckCommittee() for backwards compatibility with StandbyCommittee.
func (r *RoleRegistry) CheckCommittee(ic *interop.Context) bool {
	caller := ic.VM.GetCallingScriptHash()

	// Check if caller has COMMITTEE role in RoleRegistry
	if r.HasRoleInternal(ic.DAO, caller, RoleCommittee, ic.Block.Index) {
		return true
	}

	// Check if caller is in the tutusCommittee config
	for _, addr := range r.tutusCommittee {
		if addr.Equals(caller) {
			return true
		}
	}

	// Fallback to Tutus.CheckCommittee for backwards compatibility
	// This allows StandbyCommittee to work when TutusCommittee is not configured
	if r.Tutus != nil && r.Tutus.CheckCommittee(ic) {
		return true
	}

	return false
}

// CheckCommitteeWitness checks if the caller has committee signature.
func (r *RoleRegistry) checkCommitteeWitness(ic *interop.Context) error {
	if !r.CheckCommittee(ic) {
		// Fall back to checking if any TutusCommittee member signed the transaction
		for _, addr := range r.tutusCommittee {
			if ok, _ := runtime.CheckHashedWitness(ic, addr); ok {
				return nil
			}
		}
		// Check addresses that have been granted COMMITTEE role
		committeeAddrs := r.getAddressesWithRole(ic.DAO, RoleCommittee)
		for _, addr := range committeeAddrs {
			if ok, _ := runtime.CheckHashedWitness(ic, addr); ok {
				return nil
			}
		}
		// Fallback to Tutus.CheckCommittee for backwards compatibility
		// This allows StandbyCommittee to work when TutusCommittee is not configured
		if r.Tutus != nil && r.Tutus.CheckCommittee(ic) {
			return nil
		}
		return ErrRoleRegistryNotCommittee
	}
	return nil
}

// HasRoleInternal checks if an address has a specific role (including hierarchy).
// A user has a role if they have it directly, OR if they have a child role that inherits from it.
func (r *RoleRegistry) HasRoleInternal(d *dao.Simple, address util.Uint160, roleID uint64, blockHeight uint32) bool {
	// Get all roles directly assigned to this address
	assignedRoleIDs := r.getAddressRoleIDs(d, address)

	for _, assignedRoleID := range assignedRoleIDs {
		// Check if assignment is valid
		assignment := r.getAddressRoleAssignment(d, address, assignedRoleID)
		if assignment == nil || !assignment.Active {
			continue
		}
		if assignment.ExpiresAt != 0 && assignment.ExpiresAt <= blockHeight {
			continue
		}

		// Check if this assigned role equals the queried role, or inherits from it
		if r.roleInheritsFrom(d, assignedRoleID, roleID) {
			return true
		}
	}

	return false
}

// roleInheritsFrom checks if roleID equals targetRoleID, or if roleID has targetRoleID as an ancestor.
func (r *RoleRegistry) roleInheritsFrom(d *dao.Simple, roleID, targetRoleID uint64) bool {
	// Direct match
	if roleID == targetRoleID {
		return true
	}

	// Traverse up the hierarchy
	role := r.getRoleInternal(d, roleID)
	if role != nil && role.ParentID != 0 {
		return r.roleInheritsFrom(d, role.ParentID, targetRoleID)
	}

	return false
}

// HasPermissionInternal checks if an address has a specific permission via roles.
func (r *RoleRegistry) HasPermissionInternal(d *dao.Simple, address util.Uint160, resource, action string, scope state.Scope, blockHeight uint32) bool {
	// Get all roles for this address
	roleIDs := r.getAddressRoleIDs(d, address)

	for _, roleID := range roleIDs {
		// Check if assignment is valid
		assignment := r.getAddressRoleAssignment(d, address, roleID)
		if assignment == nil || !assignment.Active {
			continue
		}
		if assignment.ExpiresAt != 0 && assignment.ExpiresAt <= blockHeight {
			continue
		}

		// Check if role has the permission
		if r.roleHasPermission(d, roleID, resource, action, scope) {
			return true
		}
	}

	return false
}

// roleHasPermission checks if a role (or its parents) has a specific permission.
func (r *RoleRegistry) roleHasPermission(d *dao.Simple, roleID uint64, resource, action string, scope state.Scope) bool {
	// COMMITTEE role has all permissions
	if roleID == RoleCommittee {
		return true
	}

	// Check direct permission
	perm := r.getPermission(d, roleID, resource, action)
	if perm != nil && perm.Scope <= scope {
		return true
	}

	// Check parent roles
	role := r.getRoleInternal(d, roleID)
	if role != nil && role.ParentID != 0 {
		return r.roleHasPermission(d, role.ParentID, resource, action, scope)
	}

	return false
}

// Storage helpers

func (r *RoleRegistry) makeRoleKey(roleID uint64) []byte {
	key := make([]byte, 9)
	key[0] = rrPrefixRole
	binary.BigEndian.PutUint64(key[1:], roleID)
	return key
}

func (r *RoleRegistry) makeRoleNameKey(name string) []byte {
	key := make([]byte, 1+len(name))
	key[0] = rrPrefixRoleName
	copy(key[1:], []byte(name))
	return key
}

func (r *RoleRegistry) makeAddressRoleKey(address util.Uint160, roleID uint64) []byte {
	key := make([]byte, 1+20+8)
	key[0] = rrPrefixAddressAssignment
	copy(key[1:21], address.BytesBE())
	binary.BigEndian.PutUint64(key[21:], roleID)
	return key
}

func (r *RoleRegistry) makeAddressRolesKey(address util.Uint160) []byte {
	key := make([]byte, 1+20)
	key[0] = rrPrefixAddressRoles
	copy(key[1:], address.BytesBE())
	return key
}

func (r *RoleRegistry) makePermissionKey(roleID uint64, resource, action string) []byte {
	key := make([]byte, 1+8+len(resource)+1+len(action))
	key[0] = rrPrefixPermission
	binary.BigEndian.PutUint64(key[1:9], roleID)
	copy(key[9:9+len(resource)], resource)
	key[9+len(resource)] = 0 // separator
	copy(key[10+len(resource):], action)
	return key
}

func (r *RoleRegistry) putRole(d *dao.Simple, role *state.Role) error {
	key := r.makeRoleKey(role.ID)
	return putConvertibleToDAO(r.ID, d, key, role)
}

func (r *RoleRegistry) getRoleInternal(d *dao.Simple, roleID uint64) *state.Role {
	key := r.makeRoleKey(roleID)
	si := d.GetStorageItem(r.ID, key)
	if si == nil {
		return nil
	}
	role := new(state.Role)
	err := stackitem.DeserializeConvertible(si, role)
	if err != nil {
		return nil
	}
	return role
}

func (r *RoleRegistry) putRoleNameIndex(d *dao.Simple, name string, roleID uint64) {
	key := r.makeRoleNameKey(name)
	data := make([]byte, 8)
	binary.BigEndian.PutUint64(data, roleID)
	d.PutStorageItem(r.ID, key, data)
}

func (r *RoleRegistry) getRoleIDByName(d *dao.Simple, name string) (uint64, bool) {
	key := r.makeRoleNameKey(name)
	si := d.GetStorageItem(r.ID, key)
	if si == nil {
		return 0, false
	}
	return binary.BigEndian.Uint64(si), true
}

func (r *RoleRegistry) putAddressRoleAssignment(d *dao.Simple, assignment *state.AddressRoleAssignment) error {
	key := r.makeAddressRoleKey(assignment.Address, assignment.RoleID)
	err := putConvertibleToDAO(r.ID, d, key, assignment)
	if err != nil {
		return err
	}
	// Update address roles index
	r.addAddressRoleIndex(d, assignment.Address, assignment.RoleID)
	return nil
}

func (r *RoleRegistry) getAddressRoleAssignment(d *dao.Simple, address util.Uint160, roleID uint64) *state.AddressRoleAssignment {
	key := r.makeAddressRoleKey(address, roleID)
	si := d.GetStorageItem(r.ID, key)
	if si == nil {
		return nil
	}
	assignment := new(state.AddressRoleAssignment)
	err := stackitem.DeserializeConvertible(si, assignment)
	if err != nil {
		return nil
	}
	return assignment
}

func (r *RoleRegistry) deleteAddressRoleAssignment(d *dao.Simple, address util.Uint160, roleID uint64) {
	key := r.makeAddressRoleKey(address, roleID)
	d.DeleteStorageItem(r.ID, key)
	r.removeAddressRoleIndex(d, address, roleID)
}

func (r *RoleRegistry) addAddressRoleIndex(d *dao.Simple, address util.Uint160, roleID uint64) {
	key := r.makeAddressRolesKey(address)
	si := d.GetStorageItem(r.ID, key)
	var roleIDs []uint64
	if si != nil {
		roleIDs = r.decodeRoleIDs(si)
	}
	// Check if already exists
	for _, id := range roleIDs {
		if id == roleID {
			return
		}
	}
	roleIDs = append(roleIDs, roleID)
	d.PutStorageItem(r.ID, key, r.encodeRoleIDs(roleIDs))
}

func (r *RoleRegistry) removeAddressRoleIndex(d *dao.Simple, address util.Uint160, roleID uint64) {
	key := r.makeAddressRolesKey(address)
	si := d.GetStorageItem(r.ID, key)
	if si == nil {
		return
	}
	roleIDs := r.decodeRoleIDs(si)
	newIDs := make([]uint64, 0, len(roleIDs))
	for _, id := range roleIDs {
		if id != roleID {
			newIDs = append(newIDs, id)
		}
	}
	if len(newIDs) == 0 {
		d.DeleteStorageItem(r.ID, key)
	} else {
		d.PutStorageItem(r.ID, key, r.encodeRoleIDs(newIDs))
	}
}

func (r *RoleRegistry) getAddressRoleIDs(d *dao.Simple, address util.Uint160) []uint64 {
	key := r.makeAddressRolesKey(address)
	si := d.GetStorageItem(r.ID, key)
	if si == nil {
		return nil
	}
	return r.decodeRoleIDs(si)
}

func (r *RoleRegistry) getAddressesWithRole(d *dao.Simple, roleID uint64) []util.Uint160 {
	var addresses []util.Uint160
	prefix := []byte{rrPrefixAddressAssignment}
	d.Seek(r.ID, storage.SeekRange{Prefix: prefix}, func(k, v []byte) bool {
		if len(k) >= 28 {
			storedRoleID := binary.BigEndian.Uint64(k[20:28])
			if storedRoleID == roleID {
				addr, err := util.Uint160DecodeBytesBE(k[:20])
				if err == nil {
					assignment := new(state.AddressRoleAssignment)
					if err := stackitem.DeserializeConvertible(v, assignment); err == nil && assignment.Active {
						addresses = append(addresses, addr)
					}
				}
			}
		}
		return true
	})
	return addresses
}

func (r *RoleRegistry) encodeRoleIDs(ids []uint64) []byte {
	data := make([]byte, 8*len(ids))
	for i, id := range ids {
		binary.BigEndian.PutUint64(data[i*8:], id)
	}
	return data
}

func (r *RoleRegistry) decodeRoleIDs(data []byte) []uint64 {
	count := len(data) / 8
	ids := make([]uint64, count)
	for i := 0; i < count; i++ {
		ids[i] = binary.BigEndian.Uint64(data[i*8:])
	}
	return ids
}

func (r *RoleRegistry) putPermission(d *dao.Simple, perm *state.PermissionGrant) error {
	key := r.makePermissionKey(perm.RoleID, perm.Resource, perm.Action)
	return putConvertibleToDAO(r.ID, d, key, perm)
}

func (r *RoleRegistry) getPermission(d *dao.Simple, roleID uint64, resource, action string) *state.PermissionGrant {
	key := r.makePermissionKey(roleID, resource, action)
	si := d.GetStorageItem(r.ID, key)
	if si == nil {
		return nil
	}
	perm := new(state.PermissionGrant)
	err := stackitem.DeserializeConvertible(si, perm)
	if err != nil {
		return nil
	}
	return perm
}

func (r *RoleRegistry) deletePermission(d *dao.Simple, roleID uint64, resource, action string) {
	key := r.makePermissionKey(roleID, resource, action)
	d.DeleteStorageItem(r.ID, key)
}

func (r *RoleRegistry) getRoleCounter(d *dao.Simple) uint64 {
	key := []byte{rrPrefixCounter}
	si := d.GetStorageItem(r.ID, key)
	if si == nil {
		return 0
	}
	return binary.BigEndian.Uint64(si)
}

func (r *RoleRegistry) putRoleCounter(d *dao.Simple, count uint64) {
	key := []byte{rrPrefixCounter}
	data := make([]byte, 8)
	binary.BigEndian.PutUint64(data, count)
	d.PutStorageItem(r.ID, key, data)
}

func (r *RoleRegistry) nextRoleID(d *dao.Simple, cache *RoleRegistryCache) uint64 {
	id := r.getRoleCounter(d)
	r.putRoleCounter(d, id+1)
	cache.roleCount++
	return id
}

// Contract methods

func (r *RoleRegistry) totalRoles(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	cache := ic.DAO.GetROCache(r.ID).(*RoleRegistryCache)
	return stackitem.NewBigInteger(big.NewInt(int64(cache.roleCount)))
}

func (r *RoleRegistry) getRole(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	roleID := toBigInt(args[0]).Uint64()
	role := r.getRoleInternal(ic.DAO, roleID)
	if role == nil {
		return stackitem.Null{}
	}
	item, _ := role.ToStackItem()
	return item
}

func (r *RoleRegistry) getRoleByName(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	name := toString(args[0])
	roleID, found := r.getRoleIDByName(ic.DAO, name)
	if !found {
		return stackitem.Null{}
	}
	role := r.getRoleInternal(ic.DAO, roleID)
	if role == nil {
		return stackitem.Null{}
	}
	item, _ := role.ToStackItem()
	return item
}

func (r *RoleRegistry) hasRole(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	address := toUint160(args[0])
	roleID := toBigInt(args[1]).Uint64()
	return stackitem.NewBool(r.HasRoleInternal(ic.DAO, address, roleID, ic.Block.Index))
}

func (r *RoleRegistry) getRolesForAddress(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	address := toUint160(args[0])
	roleIDs := r.getAddressRoleIDs(ic.DAO, address)
	items := make([]stackitem.Item, len(roleIDs))
	for i, id := range roleIDs {
		items[i] = stackitem.NewBigInteger(big.NewInt(int64(id)))
	}
	return stackitem.NewArray(items)
}

func (r *RoleRegistry) getPermissions(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	roleID := toBigInt(args[0]).Uint64()
	var perms []stackitem.Item
	prefix := make([]byte, 9)
	prefix[0] = rrPrefixPermission
	binary.BigEndian.PutUint64(prefix[1:], roleID)
	ic.DAO.Seek(r.ID, storage.SeekRange{Prefix: prefix}, func(k, v []byte) bool {
		perm := new(state.PermissionGrant)
		if err := stackitem.DeserializeConvertible(v, perm); err == nil {
			item, _ := perm.ToStackItem()
			perms = append(perms, item)
		}
		return true
	})
	return stackitem.NewArray(perms)
}

func (r *RoleRegistry) hasPermission(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	address := toUint160(args[0])
	resource := toString(args[1])
	action := toString(args[2])
	scopeInt := toBigInt(args[3]).Int64()
	scope := state.Scope(scopeInt)
	return stackitem.NewBool(r.HasPermissionInternal(ic.DAO, address, resource, action, scope, ic.Block.Index))
}

func (r *RoleRegistry) createRole(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	if err := r.checkCommitteeWitness(ic); err != nil {
		panic(err)
	}

	name := toString(args[0])
	description := toString(args[1])
	parentID := toBigInt(args[2]).Uint64()

	// Validation
	if len(name) > maxRoleNameLength {
		panic(ErrRoleNameTooLong)
	}
	if len(description) > maxRoleDescriptionLength {
		panic(ErrRoleDescTooLong)
	}
	if _, exists := r.getRoleIDByName(ic.DAO, name); exists {
		panic(ErrRoleNameExists)
	}
	if parentID != 0 {
		parent := r.getRoleInternal(ic.DAO, parentID)
		if parent == nil {
			panic(ErrInvalidParentRole)
		}
		if !parent.Active {
			panic(ErrRoleNotActive)
		}
	}

	cache := ic.DAO.GetRWCache(r.ID).(*RoleRegistryCache)
	roleID := r.nextRoleID(ic.DAO, cache)

	caller := ic.VM.GetCallingScriptHash()
	role := &state.Role{
		ID:          roleID,
		Name:        name,
		Description: description,
		ParentID:    parentID,
		CreatedAt:   ic.Block.Index,
		CreatedBy:   caller,
		Active:      true,
	}

	if err := r.putRole(ic.DAO, role); err != nil {
		panic(err)
	}
	r.putRoleNameIndex(ic.DAO, name, roleID)

	err := ic.AddNotification(r.Hash, RoleCreatedEvent, stackitem.NewArray([]stackitem.Item{
		stackitem.NewBigInteger(big.NewInt(int64(roleID))),
		stackitem.NewByteArray([]byte(name)),
		stackitem.NewBigInteger(big.NewInt(int64(parentID))),
		stackitem.NewByteArray(caller.BytesBE()),
	}))
	if err != nil {
		panic(err)
	}

	return stackitem.NewBigInteger(big.NewInt(int64(roleID)))
}

func (r *RoleRegistry) deleteRole(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	if err := r.checkCommitteeWitness(ic); err != nil {
		panic(err)
	}

	roleID := toBigInt(args[0]).Uint64()

	// Cannot delete built-in roles
	if roleID <= RoleOperator {
		panic(ErrBuiltinRole)
	}

	role := r.getRoleInternal(ic.DAO, roleID)
	if role == nil {
		panic(ErrRoleNotFound)
	}

	role.Active = false
	if err := r.putRole(ic.DAO, role); err != nil {
		panic(err)
	}

	caller := ic.VM.GetCallingScriptHash()
	err := ic.AddNotification(r.Hash, RoleDeletedEvent, stackitem.NewArray([]stackitem.Item{
		stackitem.NewBigInteger(big.NewInt(int64(roleID))),
		stackitem.NewByteArray(caller.BytesBE()),
	}))
	if err != nil {
		panic(err)
	}

	return stackitem.NewBool(true)
}

func (r *RoleRegistry) grantRole(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	if err := r.checkCommitteeWitness(ic); err != nil {
		panic(err)
	}

	address := toUint160(args[0])
	roleID := toBigInt(args[1]).Uint64()
	expiresAt := uint32(toBigInt(args[2]).Uint64())

	// Verify role exists and is active
	role := r.getRoleInternal(ic.DAO, roleID)
	if role == nil {
		panic(ErrRoleNotFound)
	}
	if !role.Active {
		panic(ErrRoleNotActive)
	}

	// Check if already assigned
	existing := r.getAddressRoleAssignment(ic.DAO, address, roleID)
	if existing != nil && existing.Active {
		panic(ErrAssignmentExists)
	}

	caller := ic.VM.GetCallingScriptHash()
	assignment := &state.AddressRoleAssignment{
		Address:   address,
		RoleID:    roleID,
		GrantedAt: ic.Block.Index,
		GrantedBy: caller,
		ExpiresAt: expiresAt,
		Active:    true,
	}

	if err := r.putAddressRoleAssignment(ic.DAO, assignment); err != nil {
		panic(err)
	}

	err := ic.AddNotification(r.Hash, RoleGrantedEvent, stackitem.NewArray([]stackitem.Item{
		stackitem.NewByteArray(address.BytesBE()),
		stackitem.NewBigInteger(big.NewInt(int64(roleID))),
		stackitem.NewBigInteger(big.NewInt(int64(expiresAt))),
		stackitem.NewByteArray(caller.BytesBE()),
	}))
	if err != nil {
		panic(err)
	}

	return stackitem.NewBool(true)
}

func (r *RoleRegistry) revokeRole(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	if err := r.checkCommitteeWitness(ic); err != nil {
		panic(err)
	}

	address := toUint160(args[0])
	roleID := toBigInt(args[1]).Uint64()

	assignment := r.getAddressRoleAssignment(ic.DAO, address, roleID)
	if assignment == nil || !assignment.Active {
		panic(ErrAssignmentNotFound)
	}

	assignment.Active = false
	if err := r.putAddressRoleAssignment(ic.DAO, assignment); err != nil {
		panic(err)
	}

	caller := ic.VM.GetCallingScriptHash()
	err := ic.AddNotification(r.Hash, RoleRevokedEvent, stackitem.NewArray([]stackitem.Item{
		stackitem.NewByteArray(address.BytesBE()),
		stackitem.NewBigInteger(big.NewInt(int64(roleID))),
		stackitem.NewByteArray(caller.BytesBE()),
	}))
	if err != nil {
		panic(err)
	}

	return stackitem.NewBool(true)
}

func (r *RoleRegistry) assignPermission(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	if err := r.checkCommitteeWitness(ic); err != nil {
		panic(err)
	}

	roleID := toBigInt(args[0]).Uint64()
	resource := toString(args[1])
	action := toString(args[2])
	scopeInt := toBigInt(args[3]).Int64()
	scope := state.Scope(scopeInt)

	// Validation
	if len(resource) > maxResourceLength {
		panic(ErrResourceTooLong)
	}
	if len(action) > maxActionLength {
		panic(ErrActionTooLong)
	}
	if scope > state.ScopeDelegated {
		panic(ErrInvalidScope)
	}

	// Verify role exists and is active
	role := r.getRoleInternal(ic.DAO, roleID)
	if role == nil {
		panic(ErrRoleNotFound)
	}
	if !role.Active {
		panic(ErrRoleNotActive)
	}

	// Check if already exists
	existing := r.getPermission(ic.DAO, roleID, resource, action)
	if existing != nil {
		panic(ErrPermissionExists)
	}

	caller := ic.VM.GetCallingScriptHash()
	perm := &state.PermissionGrant{
		RoleID:    roleID,
		Resource:  resource,
		Action:    action,
		Scope:     scope,
		GrantedAt: ic.Block.Index,
		GrantedBy: caller,
	}

	if err := r.putPermission(ic.DAO, perm); err != nil {
		panic(err)
	}

	err := ic.AddNotification(r.Hash, PermissionAssignedEvent, stackitem.NewArray([]stackitem.Item{
		stackitem.NewBigInteger(big.NewInt(int64(roleID))),
		stackitem.NewByteArray([]byte(resource)),
		stackitem.NewByteArray([]byte(action)),
		stackitem.NewBigInteger(big.NewInt(int64(scope))),
	}))
	if err != nil {
		panic(err)
	}

	return stackitem.NewBool(true)
}

func (r *RoleRegistry) removePermission(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	if err := r.checkCommitteeWitness(ic); err != nil {
		panic(err)
	}

	roleID := toBigInt(args[0]).Uint64()
	resource := toString(args[1])
	action := toString(args[2])

	// Verify permission exists
	existing := r.getPermission(ic.DAO, roleID, resource, action)
	if existing == nil {
		panic(ErrPermissionNotFound)
	}

	r.deletePermission(ic.DAO, roleID, resource, action)

	err := ic.AddNotification(r.Hash, PermissionRemovedEvent, stackitem.NewArray([]stackitem.Item{
		stackitem.NewBigInteger(big.NewInt(int64(roleID))),
		stackitem.NewByteArray([]byte(resource)),
		stackitem.NewByteArray([]byte(action)),
	}))
	if err != nil {
		panic(err)
	}

	return stackitem.NewBool(true)
}
