package native

import (
	"encoding/binary"
	"errors"
	"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/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"
)

// Tribute represents the anti-hoarding economics native contract.
type Tribute struct {
	interop.ContractMD
	Tutus ITutus
	Vita         IVita
	VTS          IVTS
	RoleRegistry IRoleRegistry
	Lex          ILex
}

// TributeCache represents the cached state for Tribute contract.
type TributeCache struct {
	accountCount        uint64
	assessmentCount     uint64
	incentiveCount      uint64
	redistributionCount uint64
}

// Storage key prefixes for Tribute.
const (
	tributePrefixAccount              byte = 0x01 // vitaID -> VelocityAccount
	tributePrefixAccountByOwner       byte = 0x02 // owner -> vitaID
	tributePrefixAssessment           byte = 0x10 // assessmentID -> TributeAssessment
	tributePrefixAssessmentByOwner    byte = 0x11 // vitaID + assessmentID -> exists
	tributePrefixPendingAssessment    byte = 0x12 // vitaID -> latest pending assessmentID
	tributePrefixIncentive            byte = 0x20 // incentiveID -> CirculationIncentive
	tributePrefixIncentiveByOwner     byte = 0x21 // vitaID + incentiveID -> exists
	tributePrefixUnclaimedIncentive   byte = 0x22 // vitaID + incentiveID -> exists (unclaimed only)
	tributePrefixRedistribution       byte = 0x30 // redistID -> RedistributionRecord
	tributePrefixAccountCounter       byte = 0xF0 // -> uint64
	tributePrefixAssessmentCounter    byte = 0xF1 // -> next assessment ID
	tributePrefixIncentiveCounter     byte = 0xF2 // -> next incentive ID
	tributePrefixRedistributionCtr    byte = 0xF3 // -> next redistribution ID
	tributePrefixTotalTributePool     byte = 0xF8 // -> total tribute collected for redistribution
	tributePrefixConfig               byte = 0xFF // -> TributeConfig
)

// Event names for Tribute.
const (
	VelocityAccountCreatedEvent  = "VelocityAccountCreated"
	VelocityUpdatedEvent         = "VelocityUpdated"
	TributeAssessedEvent         = "TributeAssessed"
	TributeCollectedEvent        = "TributeCollected"
	TributeWaivedEvent           = "TributeWaived"
	TributeAppealedEvent         = "TributeAppealed"
	IncentiveGrantedEvent        = "IncentiveGranted"
	IncentiveClaimedEvent        = "IncentiveClaimed"
	RedistributionExecutedEvent  = "RedistributionExecuted"
	ExemptionGrantedEvent        = "ExemptionGranted"
	ExemptionRevokedEvent        = "ExemptionRevoked"
)

// Role constants for tribute administrators.
const (
	RoleTributeAdmin uint64 = 23 // Can manage exemptions and appeals
)

// Various errors for Tribute.
var (
	ErrTributeAccountNotFound       = errors.New("velocity account not found")
	ErrTributeAccountExists         = errors.New("velocity account already exists")
	ErrTributeAccountExempt         = errors.New("account is exempt from tribute")
	ErrTributeAccountSuspended      = errors.New("velocity account is suspended")
	ErrTributeNoVita                = errors.New("owner must have an active Vita")
	ErrTributeAssessmentNotFound    = errors.New("tribute assessment not found")
	ErrTributeAssessmentNotPending  = errors.New("assessment is not pending")
	ErrTributeAssessmentAlreadyPaid = errors.New("assessment already collected")
	ErrTributeIncentiveNotFound     = errors.New("incentive not found")
	ErrTributeIncentiveClaimed      = errors.New("incentive already claimed")
	ErrTributeInsufficientBalance   = errors.New("insufficient balance for tribute")
	ErrTributeNotCommittee          = errors.New("invalid committee signature")
	ErrTributeNotOwner              = errors.New("caller is not the owner")
	ErrTributeNotAdmin              = errors.New("caller is not an authorized tribute admin")
	ErrTributePropertyRestricted    = errors.New("property right is restricted")
	ErrTributeInvalidAmount         = errors.New("invalid amount")
	ErrTributeInvalidReason         = errors.New("invalid reason")
	ErrTributeBelowExemption        = errors.New("balance below exemption threshold")
	ErrTributeNoHoarding            = errors.New("no hoarding detected")
	ErrTributeNothingToRedistribute = errors.New("nothing to redistribute")
)

var (
	_ interop.Contract        = (*Tribute)(nil)
	_ dao.NativeContractCache = (*TributeCache)(nil)
)

// Copy implements NativeContractCache interface.
func (c *TributeCache) Copy() dao.NativeContractCache {
	return &TributeCache{
		accountCount:        c.accountCount,
		assessmentCount:     c.assessmentCount,
		incentiveCount:      c.incentiveCount,
		redistributionCount: c.redistributionCount,
	}
}

// checkCommittee checks if the caller has committee authority.
func (t *Tribute) checkCommittee(ic *interop.Context) bool {
	if t.RoleRegistry != nil {
		return t.RoleRegistry.CheckCommittee(ic)
	}
	return t.Tutus.CheckCommittee(ic)
}

// checkTributeAdmin checks if the caller has tribute admin authority.
func (t *Tribute) checkTributeAdmin(ic *interop.Context) bool {
	caller := ic.VM.GetCallingScriptHash()
	if t.RoleRegistry != nil {
		if t.RoleRegistry.HasRoleInternal(ic.DAO, caller, RoleTributeAdmin, ic.Block.Index) {
			return true
		}
	}
	// Committee members can also act as tribute admins
	return t.checkCommittee(ic)
}

// checkPropertyRight checks if subject has property rights via Lex.
func (t *Tribute) checkPropertyRight(ic *interop.Context, subject util.Uint160) bool {
	if t.Lex == nil {
		return true // Allow if Lex not available
	}
	return t.Lex.HasRightInternal(ic.DAO, subject, state.RightProperty, ic.Block.Index)
}

// newTribute creates a new Tribute native contract.
func newTribute() *Tribute {
	t := &Tribute{
		ContractMD: *interop.NewContractMD(nativenames.Tribute, nativeids.Tribute),
	}
	defer t.BuildHFSpecificMD(t.ActiveIn())

	// ===== Account Management =====

	// createVelocityAccount - Create velocity tracking account for a Vita holder
	desc := NewDescriptor("createVelocityAccount", smartcontract.BoolType,
		manifest.NewParameter("owner", smartcontract.Hash160Type))
	md := NewMethodAndPrice(t.createVelocityAccount, 1<<17, callflag.States|callflag.AllowNotify)
	t.AddMethod(md, desc)

	// getAccount - Get velocity account by owner
	desc = NewDescriptor("getAccount", smartcontract.ArrayType,
		manifest.NewParameter("owner", smartcontract.Hash160Type))
	md = NewMethodAndPrice(t.getAccount, 1<<15, callflag.ReadStates)
	t.AddMethod(md, desc)

	// getAccountByVitaID - Get account by Vita ID
	desc = NewDescriptor("getAccountByVitaID", smartcontract.ArrayType,
		manifest.NewParameter("vitaID", smartcontract.IntegerType))
	md = NewMethodAndPrice(t.getAccountByVitaID, 1<<15, callflag.ReadStates)
	t.AddMethod(md, desc)

	// recordTransaction - Record a transaction for velocity tracking
	desc = NewDescriptor("recordTransaction", smartcontract.BoolType,
		manifest.NewParameter("owner", smartcontract.Hash160Type),
		manifest.NewParameter("amount", smartcontract.IntegerType),
		manifest.NewParameter("isOutflow", smartcontract.BoolType))
	md = NewMethodAndPrice(t.recordTransaction, 1<<16, callflag.States|callflag.AllowNotify)
	t.AddMethod(md, desc)

	// getVelocity - Get current velocity score for an owner
	desc = NewDescriptor("getVelocity", smartcontract.IntegerType,
		manifest.NewParameter("owner", smartcontract.Hash160Type))
	md = NewMethodAndPrice(t.getVelocity, 1<<15, callflag.ReadStates)
	t.AddMethod(md, desc)

	// getHoardingLevel - Get current hoarding level for an owner
	desc = NewDescriptor("getHoardingLevel", smartcontract.IntegerType,
		manifest.NewParameter("owner", smartcontract.Hash160Type))
	md = NewMethodAndPrice(t.getHoardingLevel, 1<<15, callflag.ReadStates)
	t.AddMethod(md, desc)

	// ===== Exemption Management =====

	// grantExemption - Grant exemption from tribute (admin only)
	desc = NewDescriptor("grantExemption", smartcontract.BoolType,
		manifest.NewParameter("owner", smartcontract.Hash160Type),
		manifest.NewParameter("reason", smartcontract.StringType))
	md = NewMethodAndPrice(t.grantExemption, 1<<16, callflag.States|callflag.AllowNotify)
	t.AddMethod(md, desc)

	// revokeExemption - Revoke exemption from tribute (admin only)
	desc = NewDescriptor("revokeExemption", smartcontract.BoolType,
		manifest.NewParameter("owner", smartcontract.Hash160Type))
	md = NewMethodAndPrice(t.revokeExemption, 1<<16, callflag.States|callflag.AllowNotify)
	t.AddMethod(md, desc)

	// isExempt - Check if account is exempt
	desc = NewDescriptor("isExempt", smartcontract.BoolType,
		manifest.NewParameter("owner", smartcontract.Hash160Type))
	md = NewMethodAndPrice(t.isExempt, 1<<15, callflag.ReadStates)
	t.AddMethod(md, desc)

	// ===== Assessment Management =====

	// assessTribute - Assess tribute for hoarding (called periodically or on-demand)
	desc = NewDescriptor("assessTribute", smartcontract.IntegerType,
		manifest.NewParameter("owner", smartcontract.Hash160Type))
	md = NewMethodAndPrice(t.assessTribute, 1<<17, callflag.States|callflag.AllowNotify)
	t.AddMethod(md, desc)

	// getAssessment - Get assessment by ID
	desc = NewDescriptor("getAssessment", smartcontract.ArrayType,
		manifest.NewParameter("assessmentID", smartcontract.IntegerType))
	md = NewMethodAndPrice(t.getAssessment, 1<<15, callflag.ReadStates)
	t.AddMethod(md, desc)

	// getPendingAssessment - Get pending assessment for an owner
	desc = NewDescriptor("getPendingAssessment", smartcontract.ArrayType,
		manifest.NewParameter("owner", smartcontract.Hash160Type))
	md = NewMethodAndPrice(t.getPendingAssessment, 1<<15, callflag.ReadStates)
	t.AddMethod(md, desc)

	// collectTribute - Collect pending tribute
	desc = NewDescriptor("collectTribute", smartcontract.BoolType,
		manifest.NewParameter("assessmentID", smartcontract.IntegerType))
	md = NewMethodAndPrice(t.collectTribute, 1<<17, callflag.States|callflag.AllowNotify)
	t.AddMethod(md, desc)

	// waiveTribute - Waive a tribute assessment (admin only)
	desc = NewDescriptor("waiveTribute", smartcontract.BoolType,
		manifest.NewParameter("assessmentID", smartcontract.IntegerType),
		manifest.NewParameter("reason", smartcontract.StringType))
	md = NewMethodAndPrice(t.waiveTribute, 1<<16, callflag.States|callflag.AllowNotify)
	t.AddMethod(md, desc)

	// appealTribute - Appeal a tribute assessment
	desc = NewDescriptor("appealTribute", smartcontract.BoolType,
		manifest.NewParameter("assessmentID", smartcontract.IntegerType),
		manifest.NewParameter("reason", smartcontract.StringType))
	md = NewMethodAndPrice(t.appealTribute, 1<<16, callflag.States|callflag.AllowNotify)
	t.AddMethod(md, desc)

	// ===== Incentive Management =====

	// grantIncentive - Grant circulation incentive (system/admin)
	desc = NewDescriptor("grantIncentive", smartcontract.IntegerType,
		manifest.NewParameter("owner", smartcontract.Hash160Type),
		manifest.NewParameter("incentiveType", smartcontract.IntegerType),
		manifest.NewParameter("amount", smartcontract.IntegerType),
		manifest.NewParameter("reason", smartcontract.StringType))
	md = NewMethodAndPrice(t.grantIncentive, 1<<17, callflag.States|callflag.AllowNotify)
	t.AddMethod(md, desc)

	// claimIncentive - Claim a granted incentive
	desc = NewDescriptor("claimIncentive", smartcontract.BoolType,
		manifest.NewParameter("incentiveID", smartcontract.IntegerType))
	md = NewMethodAndPrice(t.claimIncentive, 1<<17, callflag.States|callflag.AllowNotify)
	t.AddMethod(md, desc)

	// getIncentive - Get incentive by ID
	desc = NewDescriptor("getIncentive", smartcontract.ArrayType,
		manifest.NewParameter("incentiveID", smartcontract.IntegerType))
	md = NewMethodAndPrice(t.getIncentive, 1<<15, callflag.ReadStates)
	t.AddMethod(md, desc)

	// getUnclaimedIncentives - Get count of unclaimed incentives for owner
	desc = NewDescriptor("getUnclaimedIncentives", smartcontract.IntegerType,
		manifest.NewParameter("owner", smartcontract.Hash160Type))
	md = NewMethodAndPrice(t.getUnclaimedIncentives, 1<<15, callflag.ReadStates)
	t.AddMethod(md, desc)

	// ===== Redistribution =====

	// redistribute - Execute wealth redistribution (committee only)
	desc = NewDescriptor("redistribute", smartcontract.IntegerType,
		manifest.NewParameter("targetCategory", smartcontract.StringType),
		manifest.NewParameter("recipientCount", smartcontract.IntegerType))
	md = NewMethodAndPrice(t.redistribute, 1<<18, callflag.States|callflag.AllowNotify)
	t.AddMethod(md, desc)

	// getRedistribution - Get redistribution record by ID
	desc = NewDescriptor("getRedistribution", smartcontract.ArrayType,
		manifest.NewParameter("redistID", smartcontract.IntegerType))
	md = NewMethodAndPrice(t.getRedistribution, 1<<15, callflag.ReadStates)
	t.AddMethod(md, desc)

	// getTributePool - Get total tribute pool available for redistribution
	desc = NewDescriptor("getTributePool", smartcontract.IntegerType)
	md = NewMethodAndPrice(t.getTributePool, 1<<15, callflag.ReadStates)
	t.AddMethod(md, desc)

	// ===== Configuration & Stats =====

	// getConfig - Get current configuration
	desc = NewDescriptor("getConfig", smartcontract.ArrayType)
	md = NewMethodAndPrice(t.getConfig, 1<<15, callflag.ReadStates)
	t.AddMethod(md, desc)

	// getTotalAccounts - Get total velocity accounts
	desc = NewDescriptor("getTotalAccounts", smartcontract.IntegerType)
	md = NewMethodAndPrice(t.getTotalAccounts, 1<<15, callflag.ReadStates)
	t.AddMethod(md, desc)

	// getTotalAssessments - Get total assessments
	desc = NewDescriptor("getTotalAssessments", smartcontract.IntegerType)
	md = NewMethodAndPrice(t.getTotalAssessments, 1<<15, callflag.ReadStates)
	t.AddMethod(md, desc)

	// getTotalIncentives - Get total incentives
	desc = NewDescriptor("getTotalIncentives", smartcontract.IntegerType)
	md = NewMethodAndPrice(t.getTotalIncentives, 1<<15, callflag.ReadStates)
	t.AddMethod(md, desc)

	// getTotalRedistributions - Get total redistributions
	desc = NewDescriptor("getTotalRedistributions", smartcontract.IntegerType)
	md = NewMethodAndPrice(t.getTotalRedistributions, 1<<15, callflag.ReadStates)
	t.AddMethod(md, desc)

	// ===== Events =====

	// VelocityAccountCreated event
	eDesc := NewEventDescriptor(VelocityAccountCreatedEvent,
		manifest.NewParameter("vitaID", smartcontract.IntegerType),
		manifest.NewParameter("owner", smartcontract.Hash160Type))
	t.AddEvent(NewEvent(eDesc))

	// VelocityUpdated event
	eDesc = NewEventDescriptor(VelocityUpdatedEvent,
		manifest.NewParameter("vitaID", smartcontract.IntegerType),
		manifest.NewParameter("newVelocity", smartcontract.IntegerType),
		manifest.NewParameter("hoardingLevel", smartcontract.IntegerType))
	t.AddEvent(NewEvent(eDesc))

	// TributeAssessed event
	eDesc = NewEventDescriptor(TributeAssessedEvent,
		manifest.NewParameter("assessmentID", smartcontract.IntegerType),
		manifest.NewParameter("vitaID", smartcontract.IntegerType),
		manifest.NewParameter("amount", smartcontract.IntegerType))
	t.AddEvent(NewEvent(eDesc))

	// TributeCollected event
	eDesc = NewEventDescriptor(TributeCollectedEvent,
		manifest.NewParameter("assessmentID", smartcontract.IntegerType),
		manifest.NewParameter("amount", smartcontract.IntegerType))
	t.AddEvent(NewEvent(eDesc))

	// TributeWaived event
	eDesc = NewEventDescriptor(TributeWaivedEvent,
		manifest.NewParameter("assessmentID", smartcontract.IntegerType),
		manifest.NewParameter("reason", smartcontract.StringType))
	t.AddEvent(NewEvent(eDesc))

	// TributeAppealed event
	eDesc = NewEventDescriptor(TributeAppealedEvent,
		manifest.NewParameter("assessmentID", smartcontract.IntegerType),
		manifest.NewParameter("reason", smartcontract.StringType))
	t.AddEvent(NewEvent(eDesc))

	// IncentiveGranted event
	eDesc = NewEventDescriptor(IncentiveGrantedEvent,
		manifest.NewParameter("incentiveID", smartcontract.IntegerType),
		manifest.NewParameter("vitaID", smartcontract.IntegerType),
		manifest.NewParameter("amount", smartcontract.IntegerType))
	t.AddEvent(NewEvent(eDesc))

	// IncentiveClaimed event
	eDesc = NewEventDescriptor(IncentiveClaimedEvent,
		manifest.NewParameter("incentiveID", smartcontract.IntegerType),
		manifest.NewParameter("amount", smartcontract.IntegerType))
	t.AddEvent(NewEvent(eDesc))

	// RedistributionExecuted event
	eDesc = NewEventDescriptor(RedistributionExecutedEvent,
		manifest.NewParameter("redistID", smartcontract.IntegerType),
		manifest.NewParameter("totalAmount", smartcontract.IntegerType),
		manifest.NewParameter("recipientCount", smartcontract.IntegerType))
	t.AddEvent(NewEvent(eDesc))

	// ExemptionGranted event
	eDesc = NewEventDescriptor(ExemptionGrantedEvent,
		manifest.NewParameter("vitaID", smartcontract.IntegerType),
		manifest.NewParameter("reason", smartcontract.StringType))
	t.AddEvent(NewEvent(eDesc))

	// ExemptionRevoked event
	eDesc = NewEventDescriptor(ExemptionRevokedEvent,
		manifest.NewParameter("vitaID", smartcontract.IntegerType))
	t.AddEvent(NewEvent(eDesc))

	return t
}

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

// Initialize initializes the Tribute contract.
func (t *Tribute) Initialize(ic *interop.Context, hf *config.Hardfork, newMD *interop.HFSpecificContractMD) error {
	if hf != t.ActiveIn() {
		return nil
	}

	// Initialize counters
	t.setAccountCounter(ic.DAO, 0)
	t.setAssessmentCounter(ic.DAO, 0)
	t.setIncentiveCounter(ic.DAO, 0)
	t.setRedistributionCounter(ic.DAO, 0)
	t.setTributePool(ic.DAO, 0)

	// Initialize config with defaults
	// Velocity in basis points (0-10000 = 0%-100%)
	cfg := &state.TributeConfig{
		VelocityThresholdMild:     5000, // Below 50% velocity = mild hoarding
		VelocityThresholdModerate: 3000, // Below 30% = moderate hoarding
		VelocityThresholdSevere:   1500, // Below 15% = severe hoarding
		VelocityThresholdExtreme:  500,  // Below 5% = extreme hoarding
		TributeRateMild:           100,  // 1% tribute for mild hoarding
		TributeRateModerate:       300,  // 3% tribute for moderate hoarding
		TributeRateSevere:         700,  // 7% tribute for severe hoarding
		TributeRateExtreme:        1500, // 15% tribute for extreme hoarding
		IncentiveRateHigh:         50,   // 0.5% incentive for high velocity
		IncentiveRateVeryHigh:     150,  // 1.5% incentive for very high velocity
		StagnancyPeriod:           86400,   // ~1 day (1-second blocks) before balance is stagnant
		AssessmentPeriod:          604800,  // ~7 days between assessments
		GracePeriod:               259200,  // ~3 days to pay tribute
		MinBalanceForTribute:      1000000, // 1 VTS minimum to assess
		ExemptionThreshold:        100000,  // 0.1 VTS exempt
	}
	t.setConfig(ic.DAO, cfg)

	// Initialize cache
	cache := &TributeCache{
		accountCount:        0,
		assessmentCount:     0,
		incentiveCount:      0,
		redistributionCount: 0,
	}
	ic.DAO.SetCache(t.ID, cache)

	return nil
}

// InitializeCache initializes the cache from storage.
func (t *Tribute) InitializeCache(_ interop.IsHardforkEnabled, blockHeight uint32, d *dao.Simple) error {
	cache := &TributeCache{
		accountCount:        t.getAccountCounter(d),
		assessmentCount:     t.getAssessmentCounter(d),
		incentiveCount:      t.getIncentiveCounter(d),
		redistributionCount: t.getRedistributionCounter(d),
	}
	d.SetCache(t.ID, cache)
	return nil
}

// OnPersist is called before block is committed.
func (t *Tribute) OnPersist(ic *interop.Context) error {
	return nil
}

// PostPersist is called after block is committed.
func (t *Tribute) PostPersist(ic *interop.Context) error {
	return nil
}

// ActiveIn returns the hardfork at which this contract is activated.
func (t *Tribute) ActiveIn() *config.Hardfork {
	return nil // Always active
}

// ===== Storage Helpers =====

func (t *Tribute) makeAccountKey(vitaID uint64) []byte {
	key := make([]byte, 9)
	key[0] = tributePrefixAccount
	binary.BigEndian.PutUint64(key[1:], vitaID)
	return key
}

func (t *Tribute) makeAccountByOwnerKey(owner util.Uint160) []byte {
	key := make([]byte, 21)
	key[0] = tributePrefixAccountByOwner
	copy(key[1:], owner.BytesBE())
	return key
}

func (t *Tribute) makeAssessmentKey(assessmentID uint64) []byte {
	key := make([]byte, 9)
	key[0] = tributePrefixAssessment
	binary.BigEndian.PutUint64(key[1:], assessmentID)
	return key
}

func (t *Tribute) makePendingAssessmentKey(vitaID uint64) []byte {
	key := make([]byte, 9)
	key[0] = tributePrefixPendingAssessment
	binary.BigEndian.PutUint64(key[1:], vitaID)
	return key
}

func (t *Tribute) makeIncentiveKey(incentiveID uint64) []byte {
	key := make([]byte, 9)
	key[0] = tributePrefixIncentive
	binary.BigEndian.PutUint64(key[1:], incentiveID)
	return key
}

func (t *Tribute) makeUnclaimedIncentiveKey(vitaID, incentiveID uint64) []byte {
	key := make([]byte, 17)
	key[0] = tributePrefixUnclaimedIncentive
	binary.BigEndian.PutUint64(key[1:], vitaID)
	binary.BigEndian.PutUint64(key[9:], incentiveID)
	return key
}

func (t *Tribute) makeRedistributionKey(redistID uint64) []byte {
	key := make([]byte, 9)
	key[0] = tributePrefixRedistribution
	binary.BigEndian.PutUint64(key[1:], redistID)
	return key
}

// ===== Counter Helpers =====

func (t *Tribute) getAccountCounter(d *dao.Simple) uint64 {
	si := d.GetStorageItem(t.ID, []byte{tributePrefixAccountCounter})
	if si == nil {
		return 0
	}
	return binary.BigEndian.Uint64(si)
}

func (t *Tribute) setAccountCounter(d *dao.Simple, count uint64) {
	buf := make([]byte, 8)
	binary.BigEndian.PutUint64(buf, count)
	d.PutStorageItem(t.ID, []byte{tributePrefixAccountCounter}, buf)
}

func (t *Tribute) getAssessmentCounter(d *dao.Simple) uint64 {
	si := d.GetStorageItem(t.ID, []byte{tributePrefixAssessmentCounter})
	if si == nil {
		return 0
	}
	return binary.BigEndian.Uint64(si)
}

func (t *Tribute) setAssessmentCounter(d *dao.Simple, count uint64) {
	buf := make([]byte, 8)
	binary.BigEndian.PutUint64(buf, count)
	d.PutStorageItem(t.ID, []byte{tributePrefixAssessmentCounter}, buf)
}

func (t *Tribute) getIncentiveCounter(d *dao.Simple) uint64 {
	si := d.GetStorageItem(t.ID, []byte{tributePrefixIncentiveCounter})
	if si == nil {
		return 0
	}
	return binary.BigEndian.Uint64(si)
}

func (t *Tribute) setIncentiveCounter(d *dao.Simple, count uint64) {
	buf := make([]byte, 8)
	binary.BigEndian.PutUint64(buf, count)
	d.PutStorageItem(t.ID, []byte{tributePrefixIncentiveCounter}, buf)
}

func (t *Tribute) getRedistributionCounter(d *dao.Simple) uint64 {
	si := d.GetStorageItem(t.ID, []byte{tributePrefixRedistributionCtr})
	if si == nil {
		return 0
	}
	return binary.BigEndian.Uint64(si)
}

func (t *Tribute) setRedistributionCounter(d *dao.Simple, count uint64) {
	buf := make([]byte, 8)
	binary.BigEndian.PutUint64(buf, count)
	d.PutStorageItem(t.ID, []byte{tributePrefixRedistributionCtr}, buf)
}

func (t *Tribute) getTributePoolValue(d *dao.Simple) uint64 {
	si := d.GetStorageItem(t.ID, []byte{tributePrefixTotalTributePool})
	if si == nil {
		return 0
	}
	return binary.BigEndian.Uint64(si)
}

func (t *Tribute) setTributePool(d *dao.Simple, amount uint64) {
	buf := make([]byte, 8)
	binary.BigEndian.PutUint64(buf, amount)
	d.PutStorageItem(t.ID, []byte{tributePrefixTotalTributePool}, buf)
}

// ===== Account Storage =====

func (t *Tribute) getAccountInternal(d *dao.Simple, vitaID uint64) (*state.VelocityAccount, error) {
	si := d.GetStorageItem(t.ID, t.makeAccountKey(vitaID))
	if si == nil {
		return nil, nil
	}
	acc := new(state.VelocityAccount)
	item, err := stackitem.Deserialize(si)
	if err != nil {
		return nil, err
	}
	if err := acc.FromStackItem(item); err != nil {
		return nil, err
	}
	return acc, nil
}

func (t *Tribute) putAccount(d *dao.Simple, acc *state.VelocityAccount) error {
	data, err := stackitem.Serialize(acc.ToStackItem())
	if err != nil {
		return err
	}
	d.PutStorageItem(t.ID, t.makeAccountKey(acc.VitaID), data)
	return nil
}

func (t *Tribute) getVitaIDByOwner(d *dao.Simple, owner util.Uint160) (uint64, bool) {
	si := d.GetStorageItem(t.ID, t.makeAccountByOwnerKey(owner))
	if si == nil {
		return 0, false
	}
	return binary.BigEndian.Uint64(si), true
}

func (t *Tribute) setOwnerMapping(d *dao.Simple, owner util.Uint160, vitaID uint64) {
	buf := make([]byte, 8)
	binary.BigEndian.PutUint64(buf, vitaID)
	d.PutStorageItem(t.ID, t.makeAccountByOwnerKey(owner), buf)
}

// ===== Assessment Storage =====

func (t *Tribute) getAssessmentInternal(d *dao.Simple, assessmentID uint64) (*state.TributeAssessment, error) {
	si := d.GetStorageItem(t.ID, t.makeAssessmentKey(assessmentID))
	if si == nil {
		return nil, nil
	}
	assess := new(state.TributeAssessment)
	item, err := stackitem.Deserialize(si)
	if err != nil {
		return nil, err
	}
	if err := assess.FromStackItem(item); err != nil {
		return nil, err
	}
	return assess, nil
}

func (t *Tribute) putAssessment(d *dao.Simple, assess *state.TributeAssessment) error {
	data, err := stackitem.Serialize(assess.ToStackItem())
	if err != nil {
		return err
	}
	d.PutStorageItem(t.ID, t.makeAssessmentKey(assess.ID), data)
	return nil
}

func (t *Tribute) getPendingAssessmentID(d *dao.Simple, vitaID uint64) (uint64, bool) {
	si := d.GetStorageItem(t.ID, t.makePendingAssessmentKey(vitaID))
	if si == nil {
		return 0, false
	}
	return binary.BigEndian.Uint64(si), true
}

func (t *Tribute) setPendingAssessmentID(d *dao.Simple, vitaID, assessmentID uint64) {
	buf := make([]byte, 8)
	binary.BigEndian.PutUint64(buf, assessmentID)
	d.PutStorageItem(t.ID, t.makePendingAssessmentKey(vitaID), buf)
}

func (t *Tribute) deletePendingAssessment(d *dao.Simple, vitaID uint64) {
	d.DeleteStorageItem(t.ID, t.makePendingAssessmentKey(vitaID))
}

// ===== Incentive Storage =====

func (t *Tribute) getIncentiveInternal(d *dao.Simple, incentiveID uint64) (*state.CirculationIncentive, error) {
	si := d.GetStorageItem(t.ID, t.makeIncentiveKey(incentiveID))
	if si == nil {
		return nil, nil
	}
	inc := new(state.CirculationIncentive)
	item, err := stackitem.Deserialize(si)
	if err != nil {
		return nil, err
	}
	if err := inc.FromStackItem(item); err != nil {
		return nil, err
	}
	return inc, nil
}

func (t *Tribute) putIncentive(d *dao.Simple, inc *state.CirculationIncentive) error {
	data, err := stackitem.Serialize(inc.ToStackItem())
	if err != nil {
		return err
	}
	d.PutStorageItem(t.ID, t.makeIncentiveKey(inc.ID), data)
	return nil
}

func (t *Tribute) addUnclaimedIncentive(d *dao.Simple, vitaID, incentiveID uint64) {
	d.PutStorageItem(t.ID, t.makeUnclaimedIncentiveKey(vitaID, incentiveID), []byte{1})
}

func (t *Tribute) removeUnclaimedIncentive(d *dao.Simple, vitaID, incentiveID uint64) {
	d.DeleteStorageItem(t.ID, t.makeUnclaimedIncentiveKey(vitaID, incentiveID))
}

// ===== Redistribution Storage =====

func (t *Tribute) getRedistributionInternal(d *dao.Simple, redistID uint64) (*state.RedistributionRecord, error) {
	si := d.GetStorageItem(t.ID, t.makeRedistributionKey(redistID))
	if si == nil {
		return nil, nil
	}
	rec := new(state.RedistributionRecord)
	item, err := stackitem.Deserialize(si)
	if err != nil {
		return nil, err
	}
	if err := rec.FromStackItem(item); err != nil {
		return nil, err
	}
	return rec, nil
}

func (t *Tribute) putRedistribution(d *dao.Simple, rec *state.RedistributionRecord) error {
	data, err := stackitem.Serialize(rec.ToStackItem())
	if err != nil {
		return err
	}
	d.PutStorageItem(t.ID, t.makeRedistributionKey(rec.ID), data)
	return nil
}

// ===== Config Storage =====

func (t *Tribute) getConfigInternal(d *dao.Simple) *state.TributeConfig {
	si := d.GetStorageItem(t.ID, []byte{tributePrefixConfig})
	if si == nil {
		return nil
	}
	cfg := new(state.TributeConfig)
	item, err := stackitem.Deserialize(si)
	if err != nil {
		return nil
	}
	if err := cfg.FromStackItem(item); err != nil {
		return nil
	}
	return cfg
}

func (t *Tribute) setConfig(d *dao.Simple, cfg *state.TributeConfig) {
	data, _ := stackitem.Serialize(cfg.ToStackItem())
	d.PutStorageItem(t.ID, []byte{tributePrefixConfig}, data)
}

// ===== Internal Helpers =====

// calculateVelocity calculates velocity based on inflow/outflow ratio.
func (t *Tribute) calculateVelocity(totalInflow, totalOutflow uint64) uint64 {
	if totalInflow == 0 {
		if totalOutflow > 0 {
			return 10000 // 100% velocity if only outflow
		}
		return 5000 // Default 50% if no activity
	}
	// Velocity = (outflow / inflow) * 10000 (basis points)
	velocity := (totalOutflow * 10000) / totalInflow
	if velocity > 10000 {
		velocity = 10000
	}
	return velocity
}

// determineHoardingLevel determines hoarding level based on velocity.
func (t *Tribute) determineHoardingLevel(velocity uint64, cfg *state.TributeConfig) state.HoardingLevel {
	if velocity >= cfg.VelocityThresholdMild {
		return state.HoardingNone
	}
	if velocity >= cfg.VelocityThresholdModerate {
		return state.HoardingMild
	}
	if velocity >= cfg.VelocityThresholdSevere {
		return state.HoardingModerate
	}
	if velocity >= cfg.VelocityThresholdExtreme {
		return state.HoardingSevere
	}
	return state.HoardingExtreme
}

// getTributeRate returns the tribute rate for a hoarding level.
func (t *Tribute) getTributeRate(level state.HoardingLevel, cfg *state.TributeConfig) uint64 {
	switch level {
	case state.HoardingMild:
		return cfg.TributeRateMild
	case state.HoardingModerate:
		return cfg.TributeRateModerate
	case state.HoardingSevere:
		return cfg.TributeRateSevere
	case state.HoardingExtreme:
		return cfg.TributeRateExtreme
	default:
		return 0
	}
}

// ===== Contract Methods =====

// createVelocityAccount creates a velocity tracking account for a Vita holder.
func (t *Tribute) createVelocityAccount(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	owner := toUint160(args[0])

	// Verify owner has active Vita
	if t.Vita == nil {
		panic(ErrTributeNoVita)
	}
	vita, err := t.Vita.GetTokenByOwner(ic.DAO, owner)
	if err != nil || vita == nil {
		panic(ErrTributeNoVita)
	}
	if vita.Status != state.TokenStatusActive {
		panic(ErrTributeNoVita)
	}
	vitaID := vita.TokenID

	// Check if account already exists
	existing, _ := t.getAccountInternal(ic.DAO, vitaID)
	if existing != nil {
		panic(ErrTributeAccountExists)
	}

	// Create account
	blockHeight := ic.Block.Index
	acc := &state.VelocityAccount{
		VitaID:              vitaID,
		Owner:               owner,
		CurrentVelocity:     5000, // Default 50% velocity
		AverageVelocity:     5000,
		LastActivityBlock:   blockHeight,
		TotalInflow:         0,
		TotalOutflow:        0,
		StagnantBalance:     0,
		HoardingLevel:       state.HoardingNone,
		ExemptionReason:     "",
		TotalTributePaid:    0,
		TotalIncentivesRcvd: 0,
		Status:              state.VelocityAccountActive,
		CreatedAt:           blockHeight,
		UpdatedAt:           blockHeight,
	}

	if err := t.putAccount(ic.DAO, acc); err != nil {
		panic(err)
	}
	t.setOwnerMapping(ic.DAO, owner, vitaID)

	// Update counter
	cache := ic.DAO.GetRWCache(t.ID).(*TributeCache)
	cache.accountCount++
	t.setAccountCounter(ic.DAO, cache.accountCount)

	// Emit event
	ic.AddNotification(t.Hash, VelocityAccountCreatedEvent, stackitem.NewArray([]stackitem.Item{
		stackitem.NewBigInteger(new(big.Int).SetUint64(vitaID)),
		stackitem.NewByteArray(owner.BytesBE()),
	}))

	return stackitem.NewBool(true)
}

// getAccount returns velocity account by owner.
func (t *Tribute) getAccount(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	owner := toUint160(args[0])

	vitaID, found := t.getVitaIDByOwner(ic.DAO, owner)
	if !found {
		return stackitem.Null{}
	}

	acc, err := t.getAccountInternal(ic.DAO, vitaID)
	if err != nil || acc == nil {
		return stackitem.Null{}
	}

	return acc.ToStackItem()
}

// getAccountByVitaID returns velocity account by Vita ID.
func (t *Tribute) getAccountByVitaID(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	vitaID := toBigInt(args[0]).Uint64()

	acc, err := t.getAccountInternal(ic.DAO, vitaID)
	if err != nil || acc == nil {
		return stackitem.Null{}
	}

	return acc.ToStackItem()
}

// recordTransaction records a transaction for velocity tracking.
func (t *Tribute) recordTransaction(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	owner := toUint160(args[0])
	amount := toBigInt(args[1]).Uint64()
	isOutflow := toBool(args[2])

	if amount == 0 {
		panic(ErrTributeInvalidAmount)
	}

	vitaID, found := t.getVitaIDByOwner(ic.DAO, owner)
	if !found {
		panic(ErrTributeAccountNotFound)
	}

	acc, err := t.getAccountInternal(ic.DAO, vitaID)
	if err != nil || acc == nil {
		panic(ErrTributeAccountNotFound)
	}

	if acc.Status == state.VelocityAccountSuspended {
		panic(ErrTributeAccountSuspended)
	}

	// Update transaction totals
	if isOutflow {
		acc.TotalOutflow += amount
	} else {
		acc.TotalInflow += amount
	}

	// Recalculate velocity
	acc.CurrentVelocity = t.calculateVelocity(acc.TotalInflow, acc.TotalOutflow)

	// Update rolling average (simple average for now)
	acc.AverageVelocity = (acc.AverageVelocity + acc.CurrentVelocity) / 2

	// Determine hoarding level
	cfg := t.getConfigInternal(ic.DAO)
	acc.HoardingLevel = t.determineHoardingLevel(acc.CurrentVelocity, cfg)

	// Update timestamp
	acc.LastActivityBlock = ic.Block.Index
	acc.UpdatedAt = ic.Block.Index

	if err := t.putAccount(ic.DAO, acc); err != nil {
		panic(err)
	}

	// Emit event
	ic.AddNotification(t.Hash, VelocityUpdatedEvent, stackitem.NewArray([]stackitem.Item{
		stackitem.NewBigInteger(new(big.Int).SetUint64(vitaID)),
		stackitem.NewBigInteger(new(big.Int).SetUint64(acc.CurrentVelocity)),
		stackitem.NewBigInteger(new(big.Int).SetUint64(uint64(acc.HoardingLevel))),
	}))

	return stackitem.NewBool(true)
}

// getVelocity returns current velocity score for an owner.
func (t *Tribute) getVelocity(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	owner := toUint160(args[0])

	vitaID, found := t.getVitaIDByOwner(ic.DAO, owner)
	if !found {
		return stackitem.NewBigInteger(big.NewInt(0))
	}

	acc, err := t.getAccountInternal(ic.DAO, vitaID)
	if err != nil || acc == nil {
		return stackitem.NewBigInteger(big.NewInt(0))
	}

	return stackitem.NewBigInteger(new(big.Int).SetUint64(acc.CurrentVelocity))
}

// getHoardingLevel returns current hoarding level for an owner.
func (t *Tribute) getHoardingLevel(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	owner := toUint160(args[0])

	vitaID, found := t.getVitaIDByOwner(ic.DAO, owner)
	if !found {
		return stackitem.NewBigInteger(big.NewInt(0))
	}

	acc, err := t.getAccountInternal(ic.DAO, vitaID)
	if err != nil || acc == nil {
		return stackitem.NewBigInteger(big.NewInt(0))
	}

	return stackitem.NewBigInteger(new(big.Int).SetUint64(uint64(acc.HoardingLevel)))
}

// grantExemption grants exemption from tribute (admin only).
func (t *Tribute) grantExemption(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	owner := toUint160(args[0])
	reason := toString(args[1])

	if !t.checkTributeAdmin(ic) {
		panic(ErrTributeNotAdmin)
	}

	if len(reason) == 0 || len(reason) > 256 {
		panic(ErrTributeInvalidReason)
	}

	vitaID, found := t.getVitaIDByOwner(ic.DAO, owner)
	if !found {
		panic(ErrTributeAccountNotFound)
	}

	acc, err := t.getAccountInternal(ic.DAO, vitaID)
	if err != nil || acc == nil {
		panic(ErrTributeAccountNotFound)
	}

	acc.Status = state.VelocityAccountExempt
	acc.ExemptionReason = reason
	acc.UpdatedAt = ic.Block.Index

	if err := t.putAccount(ic.DAO, acc); err != nil {
		panic(err)
	}

	// Emit event
	ic.AddNotification(t.Hash, ExemptionGrantedEvent, stackitem.NewArray([]stackitem.Item{
		stackitem.NewBigInteger(new(big.Int).SetUint64(vitaID)),
		stackitem.NewByteArray([]byte(reason)),
	}))

	return stackitem.NewBool(true)
}

// revokeExemption revokes exemption from tribute (admin only).
func (t *Tribute) revokeExemption(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	owner := toUint160(args[0])

	if !t.checkTributeAdmin(ic) {
		panic(ErrTributeNotAdmin)
	}

	vitaID, found := t.getVitaIDByOwner(ic.DAO, owner)
	if !found {
		panic(ErrTributeAccountNotFound)
	}

	acc, err := t.getAccountInternal(ic.DAO, vitaID)
	if err != nil || acc == nil {
		panic(ErrTributeAccountNotFound)
	}

	if acc.Status != state.VelocityAccountExempt {
		panic(ErrTributeAccountNotFound)
	}

	acc.Status = state.VelocityAccountActive
	acc.ExemptionReason = ""
	acc.UpdatedAt = ic.Block.Index

	if err := t.putAccount(ic.DAO, acc); err != nil {
		panic(err)
	}

	// Emit event
	ic.AddNotification(t.Hash, ExemptionRevokedEvent, stackitem.NewArray([]stackitem.Item{
		stackitem.NewBigInteger(new(big.Int).SetUint64(vitaID)),
	}))

	return stackitem.NewBool(true)
}

// isExempt checks if account is exempt from tribute.
func (t *Tribute) isExempt(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	owner := toUint160(args[0])

	vitaID, found := t.getVitaIDByOwner(ic.DAO, owner)
	if !found {
		return stackitem.NewBool(false)
	}

	acc, err := t.getAccountInternal(ic.DAO, vitaID)
	if err != nil || acc == nil {
		return stackitem.NewBool(false)
	}

	return stackitem.NewBool(acc.Status == state.VelocityAccountExempt)
}

// assessTribute assesses tribute for hoarding.
func (t *Tribute) assessTribute(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	owner := toUint160(args[0])

	vitaID, found := t.getVitaIDByOwner(ic.DAO, owner)
	if !found {
		panic(ErrTributeAccountNotFound)
	}

	acc, err := t.getAccountInternal(ic.DAO, vitaID)
	if err != nil || acc == nil {
		panic(ErrTributeAccountNotFound)
	}

	if acc.Status == state.VelocityAccountExempt {
		panic(ErrTributeAccountExempt)
	}

	if acc.Status == state.VelocityAccountSuspended {
		panic(ErrTributeAccountSuspended)
	}

	// Check if there's already a pending assessment
	if existingID, exists := t.getPendingAssessmentID(ic.DAO, vitaID); exists {
		existing, _ := t.getAssessmentInternal(ic.DAO, existingID)
		if existing != nil && existing.Status == state.AssessmentPending {
			panic("pending assessment already exists")
		}
	}

	// Check hoarding level
	if acc.HoardingLevel == state.HoardingNone {
		panic(ErrTributeNoHoarding)
	}

	cfg := t.getConfigInternal(ic.DAO)

	// Get stagnant balance (simplified: use total inflow - outflow as approximation)
	stagnantBalance := uint64(0)
	if acc.TotalInflow > acc.TotalOutflow {
		stagnantBalance = acc.TotalInflow - acc.TotalOutflow
	}

	if stagnantBalance < cfg.MinBalanceForTribute {
		panic(ErrTributeBelowExemption)
	}

	// Calculate tribute
	tributeRate := t.getTributeRate(acc.HoardingLevel, cfg)
	tributeAmount := (stagnantBalance * tributeRate) / 10000

	// Create assessment
	cache := ic.DAO.GetRWCache(t.ID).(*TributeCache)
	assessmentID := cache.assessmentCount
	cache.assessmentCount++
	t.setAssessmentCounter(ic.DAO, cache.assessmentCount)

	blockHeight := ic.Block.Index
	assessment := &state.TributeAssessment{
		ID:              assessmentID,
		VitaID:          vitaID,
		Owner:           owner,
		AssessmentBlock: blockHeight,
		HoardingLevel:   acc.HoardingLevel,
		StagnantAmount:  stagnantBalance,
		TributeRate:     tributeRate,
		TributeAmount:   tributeAmount,
		DueBlock:        blockHeight + cfg.GracePeriod,
		CollectedBlock:  0,
		Status:          state.AssessmentPending,
		AppealReason:    "",
	}

	if err := t.putAssessment(ic.DAO, assessment); err != nil {
		panic(err)
	}
	t.setPendingAssessmentID(ic.DAO, vitaID, assessmentID)

	// Emit event
	ic.AddNotification(t.Hash, TributeAssessedEvent, stackitem.NewArray([]stackitem.Item{
		stackitem.NewBigInteger(new(big.Int).SetUint64(assessmentID)),
		stackitem.NewBigInteger(new(big.Int).SetUint64(vitaID)),
		stackitem.NewBigInteger(new(big.Int).SetUint64(tributeAmount)),
	}))

	return stackitem.NewBigInteger(new(big.Int).SetUint64(assessmentID))
}

// getAssessment returns assessment by ID.
func (t *Tribute) getAssessment(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	assessmentID := toBigInt(args[0]).Uint64()

	assess, err := t.getAssessmentInternal(ic.DAO, assessmentID)
	if err != nil || assess == nil {
		return stackitem.Null{}
	}

	return assess.ToStackItem()
}

// getPendingAssessment returns pending assessment for an owner.
func (t *Tribute) getPendingAssessment(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	owner := toUint160(args[0])

	vitaID, found := t.getVitaIDByOwner(ic.DAO, owner)
	if !found {
		return stackitem.Null{}
	}

	assessmentID, exists := t.getPendingAssessmentID(ic.DAO, vitaID)
	if !exists {
		return stackitem.Null{}
	}

	assess, err := t.getAssessmentInternal(ic.DAO, assessmentID)
	if err != nil || assess == nil {
		return stackitem.Null{}
	}

	if assess.Status != state.AssessmentPending {
		return stackitem.Null{}
	}

	return assess.ToStackItem()
}

// collectTribute collects pending tribute.
func (t *Tribute) collectTribute(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	assessmentID := toBigInt(args[0]).Uint64()

	assess, err := t.getAssessmentInternal(ic.DAO, assessmentID)
	if err != nil || assess == nil {
		panic(ErrTributeAssessmentNotFound)
	}

	if assess.Status != state.AssessmentPending {
		panic(ErrTributeAssessmentNotPending)
	}

	// Check property right before taking tribute
	if !t.checkPropertyRight(ic, assess.Owner) {
		panic(ErrTributePropertyRestricted)
	}

	// Mark as collected
	assess.Status = state.AssessmentCollected
	assess.CollectedBlock = ic.Block.Index

	if err := t.putAssessment(ic.DAO, assess); err != nil {
		panic(err)
	}
	t.deletePendingAssessment(ic.DAO, assess.VitaID)

	// Update account
	acc, _ := t.getAccountInternal(ic.DAO, assess.VitaID)
	if acc != nil {
		acc.TotalTributePaid += assess.TributeAmount
		acc.UpdatedAt = ic.Block.Index
		t.putAccount(ic.DAO, acc)
	}

	// Add to tribute pool for redistribution
	pool := t.getTributePoolValue(ic.DAO)
	pool += assess.TributeAmount
	t.setTributePool(ic.DAO, pool)

	// Emit event
	ic.AddNotification(t.Hash, TributeCollectedEvent, stackitem.NewArray([]stackitem.Item{
		stackitem.NewBigInteger(new(big.Int).SetUint64(assessmentID)),
		stackitem.NewBigInteger(new(big.Int).SetUint64(assess.TributeAmount)),
	}))

	return stackitem.NewBool(true)
}

// waiveTribute waives a tribute assessment (admin only).
func (t *Tribute) waiveTribute(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	assessmentID := toBigInt(args[0]).Uint64()
	reason := toString(args[1])

	if !t.checkTributeAdmin(ic) {
		panic(ErrTributeNotAdmin)
	}

	if len(reason) == 0 || len(reason) > 256 {
		panic(ErrTributeInvalidReason)
	}

	assess, err := t.getAssessmentInternal(ic.DAO, assessmentID)
	if err != nil || assess == nil {
		panic(ErrTributeAssessmentNotFound)
	}

	if assess.Status != state.AssessmentPending && assess.Status != state.AssessmentAppealed {
		panic(ErrTributeAssessmentNotPending)
	}

	assess.Status = state.AssessmentWaived
	assess.AppealReason = reason

	if err := t.putAssessment(ic.DAO, assess); err != nil {
		panic(err)
	}
	t.deletePendingAssessment(ic.DAO, assess.VitaID)

	// Emit event
	ic.AddNotification(t.Hash, TributeWaivedEvent, stackitem.NewArray([]stackitem.Item{
		stackitem.NewBigInteger(new(big.Int).SetUint64(assessmentID)),
		stackitem.NewByteArray([]byte(reason)),
	}))

	return stackitem.NewBool(true)
}

// appealTribute appeals a tribute assessment.
func (t *Tribute) appealTribute(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	assessmentID := toBigInt(args[0]).Uint64()
	reason := toString(args[1])

	if len(reason) == 0 || len(reason) > 512 {
		panic(ErrTributeInvalidReason)
	}

	assess, err := t.getAssessmentInternal(ic.DAO, assessmentID)
	if err != nil || assess == nil {
		panic(ErrTributeAssessmentNotFound)
	}

	// Check caller is owner
	ok, err := checkWitness(ic, assess.Owner)
	if err != nil || !ok {
		panic(ErrTributeNotOwner)
	}

	if assess.Status != state.AssessmentPending {
		panic(ErrTributeAssessmentNotPending)
	}

	assess.Status = state.AssessmentAppealed
	assess.AppealReason = reason

	if err := t.putAssessment(ic.DAO, assess); err != nil {
		panic(err)
	}

	// Emit event
	ic.AddNotification(t.Hash, TributeAppealedEvent, stackitem.NewArray([]stackitem.Item{
		stackitem.NewBigInteger(new(big.Int).SetUint64(assessmentID)),
		stackitem.NewByteArray([]byte(reason)),
	}))

	return stackitem.NewBool(true)
}

// grantIncentive grants circulation incentive (system/admin).
func (t *Tribute) grantIncentive(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	owner := toUint160(args[0])
	incentiveType := state.IncentiveType(toBigInt(args[1]).Uint64())
	amount := toBigInt(args[2]).Uint64()
	reason := toString(args[3])

	if !t.checkTributeAdmin(ic) {
		panic(ErrTributeNotAdmin)
	}

	if amount == 0 {
		panic(ErrTributeInvalidAmount)
	}

	if len(reason) == 0 || len(reason) > 256 {
		panic(ErrTributeInvalidReason)
	}

	vitaID, found := t.getVitaIDByOwner(ic.DAO, owner)
	if !found {
		panic(ErrTributeAccountNotFound)
	}

	acc, err := t.getAccountInternal(ic.DAO, vitaID)
	if err != nil || acc == nil {
		panic(ErrTributeAccountNotFound)
	}

	// Create incentive
	cache := ic.DAO.GetRWCache(t.ID).(*TributeCache)
	incentiveID := cache.incentiveCount
	cache.incentiveCount++
	t.setIncentiveCounter(ic.DAO, cache.incentiveCount)

	blockHeight := ic.Block.Index
	incentive := &state.CirculationIncentive{
		ID:            incentiveID,
		VitaID:        vitaID,
		Recipient:     owner,
		IncentiveType: incentiveType,
		Amount:        amount,
		Reason:        reason,
		VelocityScore: acc.CurrentVelocity,
		GrantedBlock:  blockHeight,
		ClaimedBlock:  0,
		Claimed:       false,
	}

	if err := t.putIncentive(ic.DAO, incentive); err != nil {
		panic(err)
	}
	t.addUnclaimedIncentive(ic.DAO, vitaID, incentiveID)

	// Emit event
	ic.AddNotification(t.Hash, IncentiveGrantedEvent, stackitem.NewArray([]stackitem.Item{
		stackitem.NewBigInteger(new(big.Int).SetUint64(incentiveID)),
		stackitem.NewBigInteger(new(big.Int).SetUint64(vitaID)),
		stackitem.NewBigInteger(new(big.Int).SetUint64(amount)),
	}))

	return stackitem.NewBigInteger(new(big.Int).SetUint64(incentiveID))
}

// claimIncentive claims a granted incentive.
func (t *Tribute) claimIncentive(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	incentiveID := toBigInt(args[0]).Uint64()

	incentive, err := t.getIncentiveInternal(ic.DAO, incentiveID)
	if err != nil || incentive == nil {
		panic(ErrTributeIncentiveNotFound)
	}

	// Check caller is recipient
	ok, err := checkWitness(ic, incentive.Recipient)
	if err != nil || !ok {
		panic(ErrTributeNotOwner)
	}

	if incentive.Claimed {
		panic(ErrTributeIncentiveClaimed)
	}

	// Mark as claimed
	incentive.Claimed = true
	incentive.ClaimedBlock = ic.Block.Index

	if err := t.putIncentive(ic.DAO, incentive); err != nil {
		panic(err)
	}
	t.removeUnclaimedIncentive(ic.DAO, incentive.VitaID, incentiveID)

	// Update account
	acc, _ := t.getAccountInternal(ic.DAO, incentive.VitaID)
	if acc != nil {
		acc.TotalIncentivesRcvd += incentive.Amount
		acc.UpdatedAt = ic.Block.Index
		t.putAccount(ic.DAO, acc)
	}

	// Emit event
	ic.AddNotification(t.Hash, IncentiveClaimedEvent, stackitem.NewArray([]stackitem.Item{
		stackitem.NewBigInteger(new(big.Int).SetUint64(incentiveID)),
		stackitem.NewBigInteger(new(big.Int).SetUint64(incentive.Amount)),
	}))

	return stackitem.NewBool(true)
}

// getIncentive returns incentive by ID.
func (t *Tribute) getIncentive(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	incentiveID := toBigInt(args[0]).Uint64()

	incentive, err := t.getIncentiveInternal(ic.DAO, incentiveID)
	if err != nil || incentive == nil {
		return stackitem.Null{}
	}

	return incentive.ToStackItem()
}

// getUnclaimedIncentives returns count of unclaimed incentives for owner.
func (t *Tribute) getUnclaimedIncentives(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	owner := toUint160(args[0])

	vitaID, found := t.getVitaIDByOwner(ic.DAO, owner)
	if !found {
		return stackitem.NewBigInteger(big.NewInt(0))
	}

	// Count unclaimed incentives by iterating storage
	// This is a simplified version - in production, we'd track this counter
	count := uint64(0)
	prefix := make([]byte, 9)
	prefix[0] = tributePrefixUnclaimedIncentive
	binary.BigEndian.PutUint64(prefix[1:], vitaID)

	ic.DAO.Seek(t.ID, storage.SeekRange{Prefix: prefix}, func(k, v []byte) bool {
		count++
		return true
	})

	return stackitem.NewBigInteger(new(big.Int).SetUint64(count))
}

// redistribute executes wealth redistribution (committee only).
func (t *Tribute) redistribute(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	targetCategory := toString(args[0])
	recipientCount := toBigInt(args[1]).Uint64()

	if !t.checkCommittee(ic) {
		panic(ErrTributeNotCommittee)
	}

	pool := t.getTributePoolValue(ic.DAO)
	if pool == 0 {
		panic(ErrTributeNothingToRedistribute)
	}

	if recipientCount == 0 {
		panic(ErrTributeInvalidAmount)
	}

	perCapita := pool / recipientCount

	// Create redistribution record
	cache := ic.DAO.GetRWCache(t.ID).(*TributeCache)
	redistID := cache.redistributionCount
	cache.redistributionCount++
	t.setRedistributionCounter(ic.DAO, cache.redistributionCount)

	record := &state.RedistributionRecord{
		ID:               redistID,
		SourceAssessment: 0, // Could link to specific assessment if needed
		TotalAmount:      pool,
		RecipientCount:   recipientCount,
		PerCapitaAmount:  perCapita,
		RedistBlock:      ic.Block.Index,
		TargetCategory:   targetCategory,
	}

	if err := t.putRedistribution(ic.DAO, record); err != nil {
		panic(err)
	}

	// Clear the pool (actual redistribution would happen via VTS transfers)
	t.setTributePool(ic.DAO, 0)

	// Emit event
	ic.AddNotification(t.Hash, RedistributionExecutedEvent, stackitem.NewArray([]stackitem.Item{
		stackitem.NewBigInteger(new(big.Int).SetUint64(redistID)),
		stackitem.NewBigInteger(new(big.Int).SetUint64(pool)),
		stackitem.NewBigInteger(new(big.Int).SetUint64(recipientCount)),
	}))

	return stackitem.NewBigInteger(new(big.Int).SetUint64(redistID))
}

// getRedistribution returns redistribution record by ID.
func (t *Tribute) getRedistribution(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	redistID := toBigInt(args[0]).Uint64()

	rec, err := t.getRedistributionInternal(ic.DAO, redistID)
	if err != nil || rec == nil {
		return stackitem.Null{}
	}

	return rec.ToStackItem()
}

// getTributePool returns total tribute pool available for redistribution.
func (t *Tribute) getTributePool(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
	return stackitem.NewBigInteger(new(big.Int).SetUint64(t.getTributePoolValue(ic.DAO)))
}

// getConfig returns current configuration.
func (t *Tribute) getConfig(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
	cfg := t.getConfigInternal(ic.DAO)
	if cfg == nil {
		return stackitem.Null{}
	}
	return cfg.ToStackItem()
}

// getTotalAccounts returns total velocity accounts.
func (t *Tribute) getTotalAccounts(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
	return stackitem.NewBigInteger(new(big.Int).SetUint64(t.getAccountCounter(ic.DAO)))
}

// getTotalAssessments returns total assessments.
func (t *Tribute) getTotalAssessments(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
	return stackitem.NewBigInteger(new(big.Int).SetUint64(t.getAssessmentCounter(ic.DAO)))
}

// getTotalIncentives returns total incentives.
func (t *Tribute) getTotalIncentives(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
	return stackitem.NewBigInteger(new(big.Int).SetUint64(t.getIncentiveCounter(ic.DAO)))
}

// getTotalRedistributions returns total redistributions.
func (t *Tribute) getTotalRedistributions(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
	return stackitem.NewBigInteger(new(big.Int).SetUint64(t.getRedistributionCounter(ic.DAO)))
}

// ===== Public Internal Methods =====

// GetAccountByOwner returns velocity account by owner (internal API).
func (t *Tribute) GetAccountByOwner(d *dao.Simple, owner util.Uint160) (*state.VelocityAccount, error) {
	vitaID, found := t.getVitaIDByOwner(d, owner)
	if !found {
		return nil, ErrTributeAccountNotFound
	}
	return t.getAccountInternal(d, vitaID)
}

// GetVelocity returns velocity score for an owner (internal API).
func (t *Tribute) GetVelocity(d *dao.Simple, owner util.Uint160) uint64 {
	vitaID, found := t.getVitaIDByOwner(d, owner)
	if !found {
		return 5000 // Default 50%
	}
	acc, err := t.getAccountInternal(d, vitaID)
	if err != nil || acc == nil {
		return 5000
	}
	return acc.CurrentVelocity
}

// IsHoarding returns true if owner is hoarding (internal API).
func (t *Tribute) IsHoarding(d *dao.Simple, owner util.Uint160) bool {
	vitaID, found := t.getVitaIDByOwner(d, owner)
	if !found {
		return false
	}
	acc, err := t.getAccountInternal(d, vitaID)
	if err != nil || acc == nil {
		return false
	}
	return acc.HoardingLevel != state.HoardingNone
}

// Address returns the contract address.
func (t *Tribute) Address() util.Uint160 {
	return t.Hash
}
