package state

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

	"git.marketally.com/tutus-one/tutus-chain/pkg/util"
	"git.marketally.com/tutus-one/tutus-chain/pkg/vm/stackitem"
)

// Scope represents permission scope level.
type Scope uint8

const (
	// ScopeGlobal applies the permission globally.
	ScopeGlobal Scope = 0
	// ScopePersonal applies only to the owner's own resources.
	ScopePersonal Scope = 1
	// ScopeDelegated is delegated by another token holder.
	ScopeDelegated Scope = 2
)

// Role represents a custom role definition in the RoleRegistry.
type Role struct {
	ID          uint64       // Unique sequential identifier
	Name        string       // Human-readable name (max 64 chars)
	Description string       // Description (max 256 chars)
	ParentID    uint64       // Parent role ID (0 = no parent, enables hierarchy)
	CreatedAt   uint32       // Block height when created
	CreatedBy   util.Uint160 // Creator's script hash
	Active      bool         // Whether role is active
}

// ToStackItem implements stackitem.Convertible interface.
func (r *Role) ToStackItem() (stackitem.Item, error) {
	return stackitem.NewStruct([]stackitem.Item{
		stackitem.NewBigInteger(big.NewInt(int64(r.ID))),
		stackitem.NewByteArray([]byte(r.Name)),
		stackitem.NewByteArray([]byte(r.Description)),
		stackitem.NewBigInteger(big.NewInt(int64(r.ParentID))),
		stackitem.NewBigInteger(big.NewInt(int64(r.CreatedAt))),
		stackitem.NewByteArray(r.CreatedBy.BytesBE()),
		stackitem.NewBool(r.Active),
	}), nil
}

// FromStackItem implements stackitem.Convertible interface.
func (r *Role) FromStackItem(item stackitem.Item) error {
	items, ok := item.Value().([]stackitem.Item)
	if !ok {
		return errors.New("not a struct")
	}
	if len(items) != 7 {
		return fmt.Errorf("wrong number of elements: expected 7, got %d", len(items))
	}

	id, err := items[0].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid id: %w", err)
	}
	r.ID = id.Uint64()

	nameBytes, err := items[1].TryBytes()
	if err != nil {
		return fmt.Errorf("invalid name: %w", err)
	}
	r.Name = string(nameBytes)

	descBytes, err := items[2].TryBytes()
	if err != nil {
		return fmt.Errorf("invalid description: %w", err)
	}
	r.Description = string(descBytes)

	parentID, err := items[3].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid parentID: %w", err)
	}
	r.ParentID = parentID.Uint64()

	createdAt, err := items[4].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid createdAt: %w", err)
	}
	r.CreatedAt = uint32(createdAt.Int64())

	createdByBytes, err := items[5].TryBytes()
	if err != nil {
		return fmt.Errorf("invalid createdBy: %w", err)
	}
	r.CreatedBy, err = util.Uint160DecodeBytesBE(createdByBytes)
	if err != nil {
		return fmt.Errorf("invalid createdBy hash: %w", err)
	}

	r.Active, err = items[6].TryBool()
	if err != nil {
		return fmt.Errorf("invalid active: %w", err)
	}

	return nil
}

// RoleAssignment represents a role granted to a Vita holder.
type RoleAssignment struct {
	TokenID   uint64       // Vita ID (or script hash as uint64 for address-based)
	RoleID    uint64       // Role ID
	GrantedAt uint32       // Block height when granted
	GrantedBy util.Uint160 // Granter's script hash
	ExpiresAt uint32       // Expiration block (0 = never)
	Active    bool         // Whether assignment is active
}

// ToStackItem implements stackitem.Convertible interface.
func (a *RoleAssignment) ToStackItem() (stackitem.Item, error) {
	return stackitem.NewStruct([]stackitem.Item{
		stackitem.NewBigInteger(big.NewInt(int64(a.TokenID))),
		stackitem.NewBigInteger(big.NewInt(int64(a.RoleID))),
		stackitem.NewBigInteger(big.NewInt(int64(a.GrantedAt))),
		stackitem.NewByteArray(a.GrantedBy.BytesBE()),
		stackitem.NewBigInteger(big.NewInt(int64(a.ExpiresAt))),
		stackitem.NewBool(a.Active),
	}), nil
}

// FromStackItem implements stackitem.Convertible interface.
func (a *RoleAssignment) FromStackItem(item stackitem.Item) error {
	items, ok := item.Value().([]stackitem.Item)
	if !ok {
		return errors.New("not a struct")
	}
	if len(items) != 6 {
		return fmt.Errorf("wrong number of elements: expected 6, got %d", len(items))
	}

	tokenID, err := items[0].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid tokenID: %w", err)
	}
	a.TokenID = tokenID.Uint64()

	roleID, err := items[1].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid roleID: %w", err)
	}
	a.RoleID = roleID.Uint64()

	grantedAt, err := items[2].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid grantedAt: %w", err)
	}
	a.GrantedAt = uint32(grantedAt.Int64())

	grantedByBytes, err := items[3].TryBytes()
	if err != nil {
		return fmt.Errorf("invalid grantedBy: %w", err)
	}
	a.GrantedBy, err = util.Uint160DecodeBytesBE(grantedByBytes)
	if err != nil {
		return fmt.Errorf("invalid grantedBy hash: %w", err)
	}

	expiresAt, err := items[4].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid expiresAt: %w", err)
	}
	a.ExpiresAt = uint32(expiresAt.Int64())

	a.Active, err = items[5].TryBool()
	if err != nil {
		return fmt.Errorf("invalid active: %w", err)
	}

	return nil
}

// PermissionGrant represents a permission assigned to a role.
type PermissionGrant struct {
	RoleID    uint64       // Role ID
	Resource  string       // Resource identifier (e.g., "documents")
	Action    string       // Action identifier (e.g., "read", "write")
	Scope     Scope        // Scope level
	GrantedAt uint32       // Block height when granted
	GrantedBy util.Uint160 // Granter's script hash
}

// ToStackItem implements stackitem.Convertible interface.
func (p *PermissionGrant) ToStackItem() (stackitem.Item, error) {
	return stackitem.NewStruct([]stackitem.Item{
		stackitem.NewBigInteger(big.NewInt(int64(p.RoleID))),
		stackitem.NewByteArray([]byte(p.Resource)),
		stackitem.NewByteArray([]byte(p.Action)),
		stackitem.NewBigInteger(big.NewInt(int64(p.Scope))),
		stackitem.NewBigInteger(big.NewInt(int64(p.GrantedAt))),
		stackitem.NewByteArray(p.GrantedBy.BytesBE()),
	}), nil
}

// FromStackItem implements stackitem.Convertible interface.
func (p *PermissionGrant) FromStackItem(item stackitem.Item) error {
	items, ok := item.Value().([]stackitem.Item)
	if !ok {
		return errors.New("not a struct")
	}
	if len(items) != 6 {
		return fmt.Errorf("wrong number of elements: expected 6, got %d", len(items))
	}

	roleID, err := items[0].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid roleID: %w", err)
	}
	p.RoleID = roleID.Uint64()

	resourceBytes, err := items[1].TryBytes()
	if err != nil {
		return fmt.Errorf("invalid resource: %w", err)
	}
	p.Resource = string(resourceBytes)

	actionBytes, err := items[2].TryBytes()
	if err != nil {
		return fmt.Errorf("invalid action: %w", err)
	}
	p.Action = string(actionBytes)

	scope, err := items[3].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid scope: %w", err)
	}
	p.Scope = Scope(scope.Int64())

	grantedAt, err := items[4].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid grantedAt: %w", err)
	}
	p.GrantedAt = uint32(grantedAt.Int64())

	grantedByBytes, err := items[5].TryBytes()
	if err != nil {
		return fmt.Errorf("invalid grantedBy: %w", err)
	}
	p.GrantedBy, err = util.Uint160DecodeBytesBE(grantedByBytes)
	if err != nil {
		return fmt.Errorf("invalid grantedBy hash: %w", err)
	}

	return nil
}

// AddressRoleAssignment represents a role granted directly to an address (script hash).
// This is used for bootstrapping when Vitas may not exist yet.
type AddressRoleAssignment struct {
	Address   util.Uint160 // Script hash of the address
	RoleID    uint64       // Role ID
	GrantedAt uint32       // Block height when granted
	GrantedBy util.Uint160 // Granter's script hash
	ExpiresAt uint32       // Expiration block (0 = never)
	Active    bool         // Whether assignment is active
}

// ToStackItem implements stackitem.Convertible interface.
func (a *AddressRoleAssignment) ToStackItem() (stackitem.Item, error) {
	return stackitem.NewStruct([]stackitem.Item{
		stackitem.NewByteArray(a.Address.BytesBE()),
		stackitem.NewBigInteger(big.NewInt(int64(a.RoleID))),
		stackitem.NewBigInteger(big.NewInt(int64(a.GrantedAt))),
		stackitem.NewByteArray(a.GrantedBy.BytesBE()),
		stackitem.NewBigInteger(big.NewInt(int64(a.ExpiresAt))),
		stackitem.NewBool(a.Active),
	}), nil
}

// FromStackItem implements stackitem.Convertible interface.
func (a *AddressRoleAssignment) FromStackItem(item stackitem.Item) error {
	items, ok := item.Value().([]stackitem.Item)
	if !ok {
		return errors.New("not a struct")
	}
	if len(items) != 6 {
		return fmt.Errorf("wrong number of elements: expected 6, got %d", len(items))
	}

	addressBytes, err := items[0].TryBytes()
	if err != nil {
		return fmt.Errorf("invalid address: %w", err)
	}
	a.Address, err = util.Uint160DecodeBytesBE(addressBytes)
	if err != nil {
		return fmt.Errorf("invalid address hash: %w", err)
	}

	roleID, err := items[1].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid roleID: %w", err)
	}
	a.RoleID = roleID.Uint64()

	grantedAt, err := items[2].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid grantedAt: %w", err)
	}
	a.GrantedAt = uint32(grantedAt.Int64())

	grantedByBytes, err := items[3].TryBytes()
	if err != nil {
		return fmt.Errorf("invalid grantedBy: %w", err)
	}
	a.GrantedBy, err = util.Uint160DecodeBytesBE(grantedByBytes)
	if err != nil {
		return fmt.Errorf("invalid grantedBy hash: %w", err)
	}

	expiresAt, err := items[4].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid expiresAt: %w", err)
	}
	a.ExpiresAt = uint32(expiresAt.Int64())

	a.Active, err = items[5].TryBool()
	if err != nil {
		return fmt.Errorf("invalid active: %w", err)
	}

	return nil
}
