package tutustest

import (
	"testing"

	"git.marketally.com/tutus-one/tutus-chain/pkg/core/native/nativenames"
	"git.marketally.com/tutus-one/tutus-chain/pkg/util"
	"git.marketally.com/tutus-one/tutus-chain/pkg/vm/stackitem"
	"github.com/stretchr/testify/require"
)

// Standard role IDs matching role_registry.go and role_registry_domain.go
const (
	// Basic roles
	RoleAdmin       uint64 = 1
	RoleValidator   uint64 = 2
	RoleNotary      uint64 = 3
	RoleOracle      uint64 = 4
	RoleStateRoot   uint64 = 5
	RoleNeoFSInner  uint64 = 6
	RoleNeoFSOuter  uint64 = 7
	RoleP2PNotary   uint64 = 8

	// Government service roles
	RoleEducator       uint64 = 20
	RoleHealthProvider uint64 = 21
	RoleLifeCoach      uint64 = 22
	RoleTributeAdmin   uint64 = 23
	RoleOpusSupervisor uint64 = 24
	RolePalamAuditor   uint64 = 25
	RolePalamJudge     uint64 = 26
	RoleBridgeOperator uint64 = 27
	RoleInvestmentMgr  uint64 = 28
	RoleJudge          uint64 = 29

	// Domain committee roles (CRIT-002)
	RoleCommitteeLegal      uint64 = 100
	RoleCommitteeHealth     uint64 = 101
	RoleCommitteeEducation  uint64 = 102
	RoleCommitteeEconomy    uint64 = 103
	RoleCommitteeIdentity   uint64 = 104
	RoleCommitteeGovernance uint64 = 105
)

// RoleHelper provides utilities for testing RoleRegistry operations.
type RoleHelper struct {
	*Executor
	t        testing.TB
	RoleReg  *ContractInvoker
	Designate *ContractInvoker
}

// NewRoleHelper creates a helper for testing role-based operations.
func NewRoleHelper(t testing.TB, e *Executor) *RoleHelper {
	return &RoleHelper{
		Executor:  e,
		t:         t,
		RoleReg:   e.CommitteeInvoker(e.NativeHash(t, nativenames.RoleRegistry)),
		Designate: e.CommitteeInvoker(e.NativeHash(t, nativenames.Designation)),
	}
}

// GrantRole grants a role to an account (requires committee authority).
func (r *RoleHelper) GrantRole(account util.Uint160, roleID uint64) {
	r.RoleReg.Invoke(r.t, true, "grantRole", account, roleID)
}

// RevokeRole revokes a role from an account (requires committee authority).
func (r *RoleHelper) RevokeRole(account util.Uint160, roleID uint64) {
	r.RoleReg.Invoke(r.t, true, "revokeRole", account, roleID)
}

// HasRole checks if an account has a specific role.
func (r *RoleHelper) HasRole(account util.Uint160, roleID uint64) bool {
	var hasRole bool
	r.RoleReg.InvokeAndCheck(r.t, func(t testing.TB, stack []stackitem.Item) {
		hasRole = stack[0].Value().(bool)
	}, "hasRole", account, roleID)
	return hasRole
}

// RequireRole asserts that an account has a specific role.
func (r *RoleHelper) RequireRole(account util.Uint160, roleID uint64) {
	require.True(r.t, r.HasRole(account, roleID), "account should have role %d", roleID)
}

// RequireNoRole asserts that an account does not have a specific role.
func (r *RoleHelper) RequireNoRole(account util.Uint160, roleID uint64) {
	require.False(r.t, r.HasRole(account, roleID), "account should not have role %d", roleID)
}

// GetRoleMembers returns all members with a specific role.
func (r *RoleHelper) GetRoleMembers(roleID uint64) []util.Uint160 {
	var members []util.Uint160
	r.RoleReg.InvokeAndCheck(r.t, func(t testing.TB, stack []stackitem.Item) {
		arr, ok := stack[0].Value().([]stackitem.Item)
		if !ok {
			return
		}
		for _, item := range arr {
			bs, err := item.TryBytes()
			require.NoError(t, err)
			members = append(members, util.Uint160(bs))
		}
	}, "getRoleMembers", roleID)
	return members
}

// SetupEducator grants the Educator role to an account.
func (r *RoleHelper) SetupEducator(account util.Uint160) {
	r.GrantRole(account, RoleEducator)
}

// SetupHealthProvider grants the HealthProvider role to an account.
func (r *RoleHelper) SetupHealthProvider(account util.Uint160) {
	r.GrantRole(account, RoleHealthProvider)
}

// SetupJudge grants the Judge role to an account.
func (r *RoleHelper) SetupJudge(account util.Uint160) {
	r.GrantRole(account, RoleJudge)
}

// SetupLifeCoach grants the LifeCoach role to an account.
func (r *RoleHelper) SetupLifeCoach(account util.Uint160) {
	r.GrantRole(account, RoleLifeCoach)
}

// SetupTributeAdmin grants the TributeAdmin role to an account.
func (r *RoleHelper) SetupTributeAdmin(account util.Uint160) {
	r.GrantRole(account, RoleTributeAdmin)
}

// SetupOpusSupervisor grants the OpusSupervisor role to an account.
func (r *RoleHelper) SetupOpusSupervisor(account util.Uint160) {
	r.GrantRole(account, RoleOpusSupervisor)
}

// SetupPalamAuditor grants the PalamAuditor role to an account.
func (r *RoleHelper) SetupPalamAuditor(account util.Uint160) {
	r.GrantRole(account, RolePalamAuditor)
}

// SetupPalamJudge grants the PalamJudge role to an account.
func (r *RoleHelper) SetupPalamJudge(account util.Uint160) {
	r.GrantRole(account, RolePalamJudge)
}

// SetupBridgeOperator grants the BridgeOperator role to an account.
func (r *RoleHelper) SetupBridgeOperator(account util.Uint160) {
	r.GrantRole(account, RoleBridgeOperator)
}

// SetupInvestmentManager grants the InvestmentManager role to an account.
func (r *RoleHelper) SetupInvestmentManager(account util.Uint160) {
	r.GrantRole(account, RoleInvestmentMgr)
}

// SetupDomainCommittee grants a domain committee role to an account.
func (r *RoleHelper) SetupDomainCommittee(account util.Uint160, domain string) {
	var roleID uint64
	switch domain {
	case "legal":
		roleID = RoleCommitteeLegal
	case "health":
		roleID = RoleCommitteeHealth
	case "education":
		roleID = RoleCommitteeEducation
	case "economy":
		roleID = RoleCommitteeEconomy
	case "identity":
		roleID = RoleCommitteeIdentity
	case "governance":
		roleID = RoleCommitteeGovernance
	default:
		require.Fail(r.t, "unknown domain: %s", domain)
	}
	r.GrantRole(account, roleID)
}

// RoleScenario represents a test scenario with role assignments.
type RoleScenario struct {
	helper   *RoleHelper
	accounts map[string]SingleSigner
	roles    map[string][]uint64
}

// NewRoleScenario creates a new role scenario builder.
func (r *RoleHelper) NewScenario() *RoleScenario {
	return &RoleScenario{
		helper:   r,
		accounts: make(map[string]SingleSigner),
		roles:    make(map[string][]uint64),
	}
}

// WithAccount adds a named account to the scenario.
func (s *RoleScenario) WithAccount(name string, acc SingleSigner) *RoleScenario {
	s.accounts[name] = acc
	return s
}

// WithRole assigns a role to a named account.
func (s *RoleScenario) WithRole(accountName string, roleID uint64) *RoleScenario {
	s.roles[accountName] = append(s.roles[accountName], roleID)
	return s
}

// Setup executes all role assignments in the scenario.
func (s *RoleScenario) Setup() map[string]SingleSigner {
	for name, roles := range s.roles {
		acc, ok := s.accounts[name]
		require.True(s.helper.t, ok, "account %s not found", name)
		for _, roleID := range roles {
			s.helper.GrantRole(acc.ScriptHash(), roleID)
		}
	}
	return s.accounts
}

// Get returns a named account from the scenario.
func (s *RoleScenario) Get(name string) SingleSigner {
	acc, ok := s.accounts[name]
	require.True(s.helper.t, ok, "account %s not found", name)
	return acc
}
