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/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/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"
)

// VTS storage prefixes.
const (
	vtsPrefixAccount      = 0x20 // address -> VTSBalance
	vtsPrefixVendor       = 0x21 // address -> Vendor
	vtsPrefixTotalSupply  = 0x11 // total supply storage
	vtsPrefixTaxConfig    = 0x10 // tax configuration
	vtsPrefixVendorCount  = 0x12 // vendor count
	vtsPrefixTxRecord     = 0x30 // txHash -> TransactionRecord
	vtsPrefixAccountTxs   = 0x31 // address + blockHeight -> []txHash index
	vtsPrefixTaxWithheld  = 0x32 // address + year -> cumulative tax
)

// VTSFactor is the divisor for VTS (10^8 = 100,000,000).
const VTSFactor = 100000000

// VTS represents the VTS (Value Transfer System) native contract.
// VTS is programmable money with spending restrictions and automatic tax accounting.
type VTS struct {
	interop.ContractMD

	Tutus ITutus
	RoleRegistry IRoleRegistry
	Vita         IVita
	Lex          ILex

	symbol   string
	decimals int64
	factor   int64
}

// VTSCache holds cached VTS data.
type VTSCache struct {
	vendorCount int64
}

// Copy implements dao.NativeContractCache.
func (c *VTSCache) Copy() dao.NativeContractCache {
	return &VTSCache{
		vendorCount: c.vendorCount,
	}
}

// newVTS creates a new VTS native contract.
func newVTS() *VTS {
	v := &VTS{
		symbol:   "VTS",
		decimals: 8,
		factor:   VTSFactor,
	}

	v.ContractMD = *interop.NewContractMD(nativenames.VTS, nativeids.VTS, func(m *manifest.Manifest, hf config.Hardfork) {
		m.SupportedStandards = []string{manifest.NEP17StandardName}
	})
	defer v.BuildHFSpecificMD(v.ActiveIn())

	// NEP-17 Standard Methods
	desc := NewDescriptor("symbol", smartcontract.StringType)
	md := NewMethodAndPrice(v.symbolMethod, 0, callflag.NoneFlag)
	v.AddMethod(md, desc)

	desc = NewDescriptor("decimals", smartcontract.IntegerType)
	md = NewMethodAndPrice(v.decimalsMethod, 0, callflag.NoneFlag)
	v.AddMethod(md, desc)

	desc = NewDescriptor("totalSupply", smartcontract.IntegerType)
	md = NewMethodAndPrice(v.totalSupply, 1<<15, callflag.ReadStates)
	v.AddMethod(md, desc)

	desc = NewDescriptor("balanceOf", smartcontract.IntegerType,
		manifest.NewParameter("account", smartcontract.Hash160Type))
	md = NewMethodAndPrice(v.balanceOf, 1<<15, callflag.ReadStates)
	v.AddMethod(md, desc)

	transferParams := []manifest.Parameter{
		manifest.NewParameter("from", smartcontract.Hash160Type),
		manifest.NewParameter("to", smartcontract.Hash160Type),
		manifest.NewParameter("amount", smartcontract.IntegerType),
	}
	desc = NewDescriptor("transfer", smartcontract.BoolType,
		append(transferParams, manifest.NewParameter("data", smartcontract.AnyType))...,
	)
	md = NewMethodAndPrice(v.transfer, 1<<17, callflag.States|callflag.AllowCall|callflag.AllowNotify)
	md.StorageFee = 50
	v.AddMethod(md, desc)

	// Extended Balance Methods
	desc = NewDescriptor("unrestrictedBalanceOf", smartcontract.IntegerType,
		manifest.NewParameter("account", smartcontract.Hash160Type))
	md = NewMethodAndPrice(v.unrestrictedBalanceOf, 1<<15, callflag.ReadStates)
	v.AddMethod(md, desc)

	desc = NewDescriptor("restrictedBalanceOf", smartcontract.IntegerType,
		manifest.NewParameter("account", smartcontract.Hash160Type),
		manifest.NewParameter("category", smartcontract.IntegerType))
	md = NewMethodAndPrice(v.restrictedBalanceOf, 1<<15, callflag.ReadStates)
	v.AddMethod(md, desc)

	desc = NewDescriptor("balanceDetails", smartcontract.ArrayType,
		manifest.NewParameter("account", smartcontract.Hash160Type))
	md = NewMethodAndPrice(v.balanceDetails, 1<<15, callflag.ReadStates)
	v.AddMethod(md, desc)

	// Minting Methods (Committee-Only)
	desc = NewDescriptor("mint", smartcontract.BoolType,
		manifest.NewParameter("to", smartcontract.Hash160Type),
		manifest.NewParameter("amount", smartcontract.IntegerType))
	md = NewMethodAndPrice(v.mint, 1<<17, callflag.States|callflag.AllowNotify)
	v.AddMethod(md, desc)

	desc = NewDescriptor("mintRestricted", smartcontract.BoolType,
		manifest.NewParameter("to", smartcontract.Hash160Type),
		manifest.NewParameter("amount", smartcontract.IntegerType),
		manifest.NewParameter("category", smartcontract.IntegerType))
	md = NewMethodAndPrice(v.mintRestricted, 1<<17, callflag.States|callflag.AllowNotify)
	v.AddMethod(md, desc)

	desc = NewDescriptor("burn", smartcontract.BoolType,
		manifest.NewParameter("from", smartcontract.Hash160Type),
		manifest.NewParameter("amount", smartcontract.IntegerType))
	md = NewMethodAndPrice(v.burn, 1<<17, callflag.States|callflag.AllowNotify)
	v.AddMethod(md, desc)

	desc = NewDescriptor("convertToUnrestricted", smartcontract.BoolType,
		manifest.NewParameter("account", smartcontract.Hash160Type),
		manifest.NewParameter("category", smartcontract.IntegerType),
		manifest.NewParameter("amount", smartcontract.IntegerType))
	md = NewMethodAndPrice(v.convertToUnrestricted, 1<<17, callflag.States|callflag.AllowNotify)
	v.AddMethod(md, desc)

	// Vendor Management Methods (Committee-Only)
	desc = NewDescriptor("registerVendor", smartcontract.BoolType,
		manifest.NewParameter("address", smartcontract.Hash160Type),
		manifest.NewParameter("name", smartcontract.StringType),
		manifest.NewParameter("categories", smartcontract.IntegerType),
		manifest.NewParameter("ageRestricted", smartcontract.BoolType))
	md = NewMethodAndPrice(v.registerVendor, 1<<17, callflag.States|callflag.AllowNotify)
	v.AddMethod(md, desc)

	desc = NewDescriptor("updateVendor", smartcontract.BoolType,
		manifest.NewParameter("address", smartcontract.Hash160Type),
		manifest.NewParameter("name", smartcontract.StringType),
		manifest.NewParameter("categories", smartcontract.IntegerType),
		manifest.NewParameter("ageRestricted", smartcontract.BoolType))
	md = NewMethodAndPrice(v.updateVendor, 1<<17, callflag.States|callflag.AllowNotify)
	v.AddMethod(md, desc)

	desc = NewDescriptor("deactivateVendor", smartcontract.BoolType,
		manifest.NewParameter("address", smartcontract.Hash160Type))
	md = NewMethodAndPrice(v.deactivateVendor, 1<<17, callflag.States|callflag.AllowNotify)
	v.AddMethod(md, desc)

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

	desc = NewDescriptor("isVendor", smartcontract.BoolType,
		manifest.NewParameter("address", smartcontract.Hash160Type))
	md = NewMethodAndPrice(v.isVendor, 1<<15, callflag.ReadStates)
	v.AddMethod(md, desc)

	desc = NewDescriptor("getVendorCategories", smartcontract.IntegerType,
		manifest.NewParameter("address", smartcontract.Hash160Type))
	md = NewMethodAndPrice(v.getVendorCategories, 1<<15, callflag.ReadStates)
	v.AddMethod(md, desc)

	// Spending Methods
	desc = NewDescriptor("spend", smartcontract.BoolType,
		manifest.NewParameter("from", smartcontract.Hash160Type),
		manifest.NewParameter("vendor", smartcontract.Hash160Type),
		manifest.NewParameter("amount", smartcontract.IntegerType),
		manifest.NewParameter("data", smartcontract.AnyType))
	md = NewMethodAndPrice(v.spend, 1<<17, callflag.States|callflag.AllowCall|callflag.AllowNotify)
	md.StorageFee = 50
	v.AddMethod(md, desc)

	desc = NewDescriptor("canSpendAt", smartcontract.BoolType,
		manifest.NewParameter("account", smartcontract.Hash160Type),
		manifest.NewParameter("vendor", smartcontract.Hash160Type),
		manifest.NewParameter("amount", smartcontract.IntegerType))
	md = NewMethodAndPrice(v.canSpendAt, 1<<15, callflag.ReadStates)
	v.AddMethod(md, desc)

	// Tax Methods
	desc = NewDescriptor("payWage", smartcontract.BoolType,
		manifest.NewParameter("employer", smartcontract.Hash160Type),
		manifest.NewParameter("employee", smartcontract.Hash160Type),
		manifest.NewParameter("grossAmount", smartcontract.IntegerType),
		manifest.NewParameter("taxRate", smartcontract.IntegerType))
	md = NewMethodAndPrice(v.payWage, 1<<17, callflag.States|callflag.AllowNotify)
	md.StorageFee = 100
	v.AddMethod(md, desc)

	desc = NewDescriptor("setTaxConfig", smartcontract.BoolType,
		manifest.NewParameter("incomeRate", smartcontract.IntegerType),
		manifest.NewParameter("salesRate", smartcontract.IntegerType),
		manifest.NewParameter("treasuryAddress", smartcontract.Hash160Type),
		manifest.NewParameter("exemptCategories", smartcontract.IntegerType))
	md = NewMethodAndPrice(v.setTaxConfig, 1<<17, callflag.States|callflag.AllowNotify)
	v.AddMethod(md, desc)

	desc = NewDescriptor("getTaxConfig", smartcontract.ArrayType)
	md = NewMethodAndPrice(v.getTaxConfig, 1<<15, callflag.ReadStates)
	v.AddMethod(md, desc)

	desc = NewDescriptor("issueTaxRefund", smartcontract.BoolType,
		manifest.NewParameter("account", smartcontract.Hash160Type),
		manifest.NewParameter("amount", smartcontract.IntegerType))
	md = NewMethodAndPrice(v.issueTaxRefund, 1<<17, callflag.States|callflag.AllowNotify)
	v.AddMethod(md, desc)

	// Tax Reporting Methods (read-only)
	desc = NewDescriptor("getTransactions", smartcontract.ArrayType,
		manifest.NewParameter("account", smartcontract.Hash160Type),
		manifest.NewParameter("startBlock", smartcontract.IntegerType),
		manifest.NewParameter("endBlock", smartcontract.IntegerType))
	md = NewMethodAndPrice(v.getTransactions, 1<<17, callflag.ReadStates)
	v.AddMethod(md, desc)

	desc = NewDescriptor("getIncomeForPeriod", smartcontract.IntegerType,
		manifest.NewParameter("account", smartcontract.Hash160Type),
		manifest.NewParameter("startBlock", smartcontract.IntegerType),
		manifest.NewParameter("endBlock", smartcontract.IntegerType))
	md = NewMethodAndPrice(v.getIncomeForPeriod, 1<<17, callflag.ReadStates)
	v.AddMethod(md, desc)

	desc = NewDescriptor("getTaxWithheld", smartcontract.IntegerType,
		manifest.NewParameter("account", smartcontract.Hash160Type),
		manifest.NewParameter("startBlock", smartcontract.IntegerType),
		manifest.NewParameter("endBlock", smartcontract.IntegerType))
	md = NewMethodAndPrice(v.getTaxWithheld, 1<<17, callflag.ReadStates)
	v.AddMethod(md, desc)

	desc = NewDescriptor("getDeductibleExpenses", smartcontract.IntegerType,
		manifest.NewParameter("account", smartcontract.Hash160Type),
		manifest.NewParameter("startBlock", smartcontract.IntegerType),
		manifest.NewParameter("endBlock", smartcontract.IntegerType),
		manifest.NewParameter("category", smartcontract.IntegerType))
	md = NewMethodAndPrice(v.getDeductibleExpenses, 1<<17, callflag.ReadStates)
	v.AddMethod(md, desc)

	desc = NewDescriptor("getTaxSummary", smartcontract.ArrayType,
		manifest.NewParameter("account", smartcontract.Hash160Type),
		manifest.NewParameter("startBlock", smartcontract.IntegerType),
		manifest.NewParameter("endBlock", smartcontract.IntegerType))
	md = NewMethodAndPrice(v.getTaxSummary, 1<<17, callflag.ReadStates)
	v.AddMethod(md, desc)

	// Events
	eDesc := NewEventDescriptor("Transfer", transferParams...)
	eMD := NewEvent(eDesc)
	v.AddEvent(eMD)

	eDesc = NewEventDescriptor("Mint",
		manifest.NewParameter("to", smartcontract.Hash160Type),
		manifest.NewParameter("amount", smartcontract.IntegerType),
		manifest.NewParameter("category", smartcontract.IntegerType))
	eMD = NewEvent(eDesc)
	v.AddEvent(eMD)

	eDesc = NewEventDescriptor("Burn",
		manifest.NewParameter("from", smartcontract.Hash160Type),
		manifest.NewParameter("amount", smartcontract.IntegerType))
	eMD = NewEvent(eDesc)
	v.AddEvent(eMD)

	eDesc = NewEventDescriptor("Spend",
		manifest.NewParameter("from", smartcontract.Hash160Type),
		manifest.NewParameter("vendor", smartcontract.Hash160Type),
		manifest.NewParameter("amount", smartcontract.IntegerType),
		manifest.NewParameter("categoriesUsed", smartcontract.IntegerType))
	eMD = NewEvent(eDesc)
	v.AddEvent(eMD)

	eDesc = NewEventDescriptor("VendorRegistered",
		manifest.NewParameter("address", smartcontract.Hash160Type),
		manifest.NewParameter("name", smartcontract.StringType),
		manifest.NewParameter("categories", smartcontract.IntegerType),
		manifest.NewParameter("ageRestricted", smartcontract.BoolType))
	eMD = NewEvent(eDesc)
	v.AddEvent(eMD)

	eDesc = NewEventDescriptor("VendorUpdated",
		manifest.NewParameter("address", smartcontract.Hash160Type),
		manifest.NewParameter("name", smartcontract.StringType),
		manifest.NewParameter("categories", smartcontract.IntegerType),
		manifest.NewParameter("ageRestricted", smartcontract.BoolType))
	eMD = NewEvent(eDesc)
	v.AddEvent(eMD)

	eDesc = NewEventDescriptor("VendorDeactivated",
		manifest.NewParameter("address", smartcontract.Hash160Type))
	eMD = NewEvent(eDesc)
	v.AddEvent(eMD)

	eDesc = NewEventDescriptor("TaxWithheld",
		manifest.NewParameter("from", smartcontract.Hash160Type),
		manifest.NewParameter("to", smartcontract.Hash160Type),
		manifest.NewParameter("grossAmount", smartcontract.IntegerType),
		manifest.NewParameter("taxAmount", smartcontract.IntegerType),
		manifest.NewParameter("taxRate", smartcontract.IntegerType))
	eMD = NewEvent(eDesc)
	v.AddEvent(eMD)

	eDesc = NewEventDescriptor("TaxRefunded",
		manifest.NewParameter("account", smartcontract.Hash160Type),
		manifest.NewParameter("amount", smartcontract.IntegerType))
	eMD = NewEvent(eDesc)
	v.AddEvent(eMD)

	eDesc = NewEventDescriptor("ConvertedToUnrestricted",
		manifest.NewParameter("account", smartcontract.Hash160Type),
		manifest.NewParameter("category", smartcontract.IntegerType),
		manifest.NewParameter("amount", smartcontract.IntegerType))
	eMD = NewEvent(eDesc)
	v.AddEvent(eMD)

	return v
}

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

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

	// Initialize with zero total supply (VTS is minted on-demand)
	v.putTotalSupply(ic.DAO, big.NewInt(0))

	// Initialize vendor count
	v.putVendorCount(ic.DAO, 0)

	// Initialize default tax config (no taxes by default)
	cfg := &state.TaxConfig{
		DefaultIncomeRate: 0,
		DefaultSalesRate:  0,
		TreasuryAddress:   util.Uint160{},
		ExemptCategories:  0,
	}
	v.putTaxConfig(ic.DAO, cfg)

	return nil
}

// InitializeCache implements the Contract interface.
func (v *VTS) InitializeCache(_ interop.IsHardforkEnabled, blockHeight uint32, d *dao.Simple) error {
	cache := &VTSCache{
		vendorCount: v.getVendorCountInternal(d),
	}
	d.SetCache(v.ID, cache)
	return nil
}

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

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

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

// checkCommittee verifies the caller has committee privileges.
func (v *VTS) checkCommittee(ic *interop.Context) bool {
	if v.RoleRegistry != nil {
		return v.RoleRegistry.CheckCommittee(ic)
	}
	if v.Tutus != nil {
		return v.Tutus.CheckCommittee(ic)
	}
	return false
}

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

func (v *VTS) makeAccountKey(h util.Uint160) []byte {
	return makeUint160Key(vtsPrefixAccount, h)
}

func (v *VTS) makeVendorKey(h util.Uint160) []byte {
	return makeUint160Key(vtsPrefixVendor, h)
}

var vtsTotalSupplyKey = []byte{vtsPrefixTotalSupply}
var vtsVendorCountKey = []byte{vtsPrefixVendorCount}
var vtsTaxConfigKey = []byte{vtsPrefixTaxConfig}

// ============ NEP-17 Standard Methods ============

func (v *VTS) symbolMethod(_ *interop.Context, _ []stackitem.Item) stackitem.Item {
	return stackitem.NewByteArray([]byte(v.symbol))
}

func (v *VTS) decimalsMethod(_ *interop.Context, _ []stackitem.Item) stackitem.Item {
	return stackitem.NewBigInteger(big.NewInt(v.decimals))
}

func (v *VTS) totalSupply(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
	supply := v.getTotalSupplyInternal(ic.DAO)
	return stackitem.NewBigInteger(supply)
}

func (v *VTS) getTotalSupplyInternal(d *dao.Simple) *big.Int {
	si := d.GetStorageItem(v.ID, vtsTotalSupplyKey)
	if si == nil {
		return big.NewInt(0)
	}
	bal, err := state.NEP17BalanceFromBytes(si)
	if err != nil {
		return big.NewInt(0)
	}
	return &bal.Balance
}

func (v *VTS) putTotalSupply(d *dao.Simple, supply *big.Int) {
	bal := &state.NEP17Balance{Balance: *supply}
	d.PutStorageItem(v.ID, vtsTotalSupplyKey, bal.Bytes(nil))
}

func (v *VTS) balanceOf(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	h := toUint160(args[0])
	balance := v.getBalanceInternal(ic.DAO, h)
	return stackitem.NewBigInteger(balance.Total())
}

func (v *VTS) getBalanceInternal(d *dao.Simple, h util.Uint160) *state.VTSBalance {
	key := v.makeAccountKey(h)
	si := d.GetStorageItem(v.ID, key)
	if si == nil {
		return state.NewVTSBalance()
	}

	item, err := stackitem.Deserialize(si)
	if err != nil {
		return state.NewVTSBalance()
	}

	bal := state.NewVTSBalance()
	if err := bal.FromStackItem(item); err != nil {
		return state.NewVTSBalance()
	}
	return bal
}

func (v *VTS) putBalance(d *dao.Simple, h util.Uint160, bal *state.VTSBalance) error {
	key := v.makeAccountKey(h)

	// If balance is zero, delete the entry
	if bal.Total().Sign() == 0 {
		d.DeleteStorageItem(v.ID, key)
		return nil
	}

	item, err := bal.ToStackItem()
	if err != nil {
		return err
	}
	data, err := stackitem.Serialize(item)
	if err != nil {
		return err
	}
	d.PutStorageItem(v.ID, key, data)
	return nil
}

func (v *VTS) transfer(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	from := toUint160(args[0])
	to := toUint160(args[1])
	amount := toBigInt(args[2])

	// Transfer only unrestricted balance
	err := v.transferUnrestricted(ic, from, to, amount, args[3])
	return stackitem.NewBool(err == nil)
}

func (v *VTS) transferUnrestricted(ic *interop.Context, from, to util.Uint160, amount *big.Int, data stackitem.Item) error {
	if amount.Sign() < 0 {
		return errors.New("negative amount")
	}

	// Check witness
	caller := ic.VM.GetCallingScriptHash()
	if !from.Equals(caller) {
		ok, err := checkWitness(ic, from)
		if err != nil || !ok {
			return errors.New("invalid signature")
		}
	}

	// Check property rights via Lex (if Lex is wired)
	if v.Lex != nil && !v.Lex.CheckPropertyRight(ic.DAO, from, ic.Block.Index) {
		return errors.New("property rights restricted")
	}

	if amount.Sign() == 0 {
		v.emitTransfer(ic, &from, &to, amount)
		return nil
	}

	// Get sender balance
	fromBal := v.getBalanceInternal(ic.DAO, from)
	if fromBal.Unrestricted.Cmp(amount) < 0 {
		return errors.New("insufficient unrestricted funds")
	}

	// Update sender balance
	fromBal.Unrestricted.Sub(&fromBal.Unrestricted, amount)
	if err := v.putBalance(ic.DAO, from, fromBal); err != nil {
		return err
	}

	// Update recipient balance
	toBal := v.getBalanceInternal(ic.DAO, to)
	toBal.Unrestricted.Add(&toBal.Unrestricted, amount)
	if err := v.putBalance(ic.DAO, to, toBal); err != nil {
		return err
	}

	v.emitTransfer(ic, &from, &to, amount)
	return nil
}

func (v *VTS) emitTransfer(ic *interop.Context, from, to *util.Uint160, amount *big.Int) {
	ic.AddNotification(v.Hash, "Transfer", stackitem.NewArray([]stackitem.Item{
		addrToStackItem(from),
		addrToStackItem(to),
		stackitem.NewBigInteger(amount),
	}))
}

// ============ Extended Balance Methods ============

func (v *VTS) unrestrictedBalanceOf(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	h := toUint160(args[0])
	bal := v.getBalanceInternal(ic.DAO, h)
	return stackitem.NewBigInteger(&bal.Unrestricted)
}

func (v *VTS) restrictedBalanceOf(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	h := toUint160(args[0])
	category := toUint8(args[1])
	bal := v.getBalanceInternal(ic.DAO, h)

	if amt, ok := bal.Restricted[category]; ok {
		return stackitem.NewBigInteger(amt)
	}
	return stackitem.NewBigInteger(big.NewInt(0))
}

func (v *VTS) balanceDetails(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	h := toUint160(args[0])
	bal := v.getBalanceInternal(ic.DAO, h)

	// Return: [unrestricted, [[category, amount], ...]]
	restricted := make([]stackitem.Item, 0, len(bal.Restricted))
	for cat, amt := range bal.Restricted {
		if amt.Sign() > 0 {
			restricted = append(restricted, stackitem.NewArray([]stackitem.Item{
				stackitem.NewBigInteger(big.NewInt(int64(cat))),
				stackitem.NewBigInteger(amt),
			}))
		}
	}

	return stackitem.NewArray([]stackitem.Item{
		stackitem.NewBigInteger(&bal.Unrestricted),
		stackitem.NewArray(restricted),
	})
}

// ============ Minting Methods ============

func (v *VTS) mint(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	if !v.checkCommittee(ic) {
		panic("caller is not a committee member")
	}

	to := toUint160(args[0])
	amount := toBigInt(args[1])

	if amount.Sign() <= 0 {
		panic("amount must be positive")
	}

	v.mintUnrestricted(ic, to, amount)
	return stackitem.NewBool(true)
}

func (v *VTS) mintUnrestricted(ic *interop.Context, to util.Uint160, amount *big.Int) {
	bal := v.getBalanceInternal(ic.DAO, to)
	bal.Unrestricted.Add(&bal.Unrestricted, amount)
	v.putBalance(ic.DAO, to, bal)

	// Update total supply
	supply := v.getTotalSupplyInternal(ic.DAO)
	supply.Add(supply, amount)
	v.putTotalSupply(ic.DAO, supply)

	v.emitTransfer(ic, nil, &to, amount)
	ic.AddNotification(v.Hash, "Mint", stackitem.NewArray([]stackitem.Item{
		stackitem.NewByteArray(to.BytesBE()),
		stackitem.NewBigInteger(amount),
		stackitem.NewBigInteger(big.NewInt(0)), // Category 0 = unrestricted
	}))
}

func (v *VTS) mintRestricted(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	if !v.checkCommittee(ic) {
		panic("caller is not a committee member")
	}

	to := toUint160(args[0])
	amount := toBigInt(args[1])
	category := toUint8(args[2])

	if amount.Sign() <= 0 {
		panic("amount must be positive")
	}
	if category == state.CategoryUnrestricted {
		panic("use mint() for unrestricted tokens")
	}

	// Restricted VTS requires recipient to have a Vita
	// This ensures benefits go to verified identities
	if !v.Vita.TokenExists(ic.DAO, to) {
		panic("restricted VTS requires Vita")
	}

	v.mintRestrictedInternal(ic, to, amount, category)
	return stackitem.NewBool(true)
}

func (v *VTS) mintRestrictedInternal(ic *interop.Context, to util.Uint160, amount *big.Int, category uint8) {
	bal := v.getBalanceInternal(ic.DAO, to)
	if bal.Restricted == nil {
		bal.Restricted = make(map[uint8]*big.Int)
	}
	if bal.Restricted[category] == nil {
		bal.Restricted[category] = big.NewInt(0)
	}
	bal.Restricted[category].Add(bal.Restricted[category], amount)
	v.putBalance(ic.DAO, to, bal)

	// Update total supply
	supply := v.getTotalSupplyInternal(ic.DAO)
	supply.Add(supply, amount)
	v.putTotalSupply(ic.DAO, supply)

	v.emitTransfer(ic, nil, &to, amount)
	ic.AddNotification(v.Hash, "Mint", stackitem.NewArray([]stackitem.Item{
		stackitem.NewByteArray(to.BytesBE()),
		stackitem.NewBigInteger(amount),
		stackitem.NewBigInteger(big.NewInt(int64(category))),
	}))
}

func (v *VTS) burn(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	if !v.checkCommittee(ic) {
		panic("caller is not a committee member")
	}

	from := toUint160(args[0])
	amount := toBigInt(args[1])

	if amount.Sign() <= 0 {
		panic("amount must be positive")
	}

	bal := v.getBalanceInternal(ic.DAO, from)
	if bal.Unrestricted.Cmp(amount) < 0 {
		panic("insufficient unrestricted funds")
	}

	bal.Unrestricted.Sub(&bal.Unrestricted, amount)
	v.putBalance(ic.DAO, from, bal)

	// Update total supply
	supply := v.getTotalSupplyInternal(ic.DAO)
	supply.Sub(supply, amount)
	v.putTotalSupply(ic.DAO, supply)

	v.emitTransfer(ic, &from, nil, amount)
	ic.AddNotification(v.Hash, "Burn", stackitem.NewArray([]stackitem.Item{
		stackitem.NewByteArray(from.BytesBE()),
		stackitem.NewBigInteger(amount),
	}))

	return stackitem.NewBool(true)
}

func (v *VTS) convertToUnrestricted(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	if !v.checkCommittee(ic) {
		panic("caller is not a committee member")
	}

	account := toUint160(args[0])
	category := toUint8(args[1])
	amount := toBigInt(args[2])

	if amount.Sign() <= 0 {
		panic("amount must be positive")
	}

	bal := v.getBalanceInternal(ic.DAO, account)
	if bal.Restricted[category] == nil || bal.Restricted[category].Cmp(amount) < 0 {
		panic("insufficient restricted funds in category")
	}

	// Move from restricted to unrestricted
	bal.Restricted[category].Sub(bal.Restricted[category], amount)
	if bal.Restricted[category].Sign() == 0 {
		delete(bal.Restricted, category)
	}
	bal.Unrestricted.Add(&bal.Unrestricted, amount)

	v.putBalance(ic.DAO, account, bal)

	ic.AddNotification(v.Hash, "ConvertedToUnrestricted", stackitem.NewArray([]stackitem.Item{
		stackitem.NewByteArray(account.BytesBE()),
		stackitem.NewBigInteger(big.NewInt(int64(category))),
		stackitem.NewBigInteger(amount),
	}))

	return stackitem.NewBool(true)
}

// ============ Vendor Management ============

func (v *VTS) registerVendor(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	if !v.checkCommittee(ic) {
		panic("caller is not a committee member")
	}

	address := toUint160(args[0])
	name := toString(args[1])
	categories := toUint8(args[2])
	ageRestricted := toBool(args[3])

	if len(name) == 0 || len(name) > 64 {
		panic("invalid vendor name")
	}

	// Check if vendor already exists
	if v.getVendorInternal(ic.DAO, address) != nil {
		panic("vendor already registered")
	}

	vendor := &state.Vendor{
		Address:       address,
		Name:          name,
		Categories:    categories,
		RegisteredAt:  ic.Block.Index,
		RegisteredBy:  ic.VM.GetCallingScriptHash(),
		Active:        true,
		AgeRestricted: ageRestricted,
	}

	v.putVendor(ic.DAO, vendor)

	// Increment vendor count
	count := v.getVendorCountInternal(ic.DAO)
	v.putVendorCount(ic.DAO, count+1)

	ic.AddNotification(v.Hash, "VendorRegistered", stackitem.NewArray([]stackitem.Item{
		stackitem.NewByteArray(address.BytesBE()),
		stackitem.NewByteArray([]byte(name)),
		stackitem.NewBigInteger(big.NewInt(int64(categories))),
		stackitem.NewBool(ageRestricted),
	}))

	return stackitem.NewBool(true)
}

func (v *VTS) updateVendor(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	if !v.checkCommittee(ic) {
		panic("caller is not a committee member")
	}

	address := toUint160(args[0])
	name := toString(args[1])
	categories := toUint8(args[2])
	ageRestricted := toBool(args[3])

	if len(name) == 0 || len(name) > 64 {
		panic("invalid vendor name")
	}

	vendor := v.getVendorInternal(ic.DAO, address)
	if vendor == nil {
		panic("vendor not found")
	}

	vendor.Name = name
	vendor.Categories = categories
	vendor.AgeRestricted = ageRestricted
	v.putVendor(ic.DAO, vendor)

	ic.AddNotification(v.Hash, "VendorUpdated", stackitem.NewArray([]stackitem.Item{
		stackitem.NewByteArray(address.BytesBE()),
		stackitem.NewByteArray([]byte(name)),
		stackitem.NewBigInteger(big.NewInt(int64(categories))),
		stackitem.NewBool(ageRestricted),
	}))

	return stackitem.NewBool(true)
}

func (v *VTS) deactivateVendor(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	if !v.checkCommittee(ic) {
		panic("caller is not a committee member")
	}

	address := toUint160(args[0])

	vendor := v.getVendorInternal(ic.DAO, address)
	if vendor == nil {
		panic("vendor not found")
	}

	vendor.Active = false
	v.putVendor(ic.DAO, vendor)

	ic.AddNotification(v.Hash, "VendorDeactivated", stackitem.NewArray([]stackitem.Item{
		stackitem.NewByteArray(address.BytesBE()),
	}))

	return stackitem.NewBool(true)
}

func (v *VTS) getVendor(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	address := toUint160(args[0])
	vendor := v.getVendorInternal(ic.DAO, address)
	if vendor == nil {
		return stackitem.Null{}
	}

	item, _ := vendor.ToStackItem()
	return item
}

func (v *VTS) getVendorInternal(d *dao.Simple, address util.Uint160) *state.Vendor {
	key := v.makeVendorKey(address)
	si := d.GetStorageItem(v.ID, key)
	if si == nil {
		return nil
	}

	item, err := stackitem.Deserialize(si)
	if err != nil {
		return nil
	}

	vendor := &state.Vendor{}
	if err := vendor.FromStackItem(item); err != nil {
		return nil
	}
	return vendor
}

func (v *VTS) putVendor(d *dao.Simple, vendor *state.Vendor) {
	key := v.makeVendorKey(vendor.Address)
	item, _ := vendor.ToStackItem()
	data, _ := stackitem.Serialize(item)
	d.PutStorageItem(v.ID, key, data)
}

func (v *VTS) isVendor(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	address := toUint160(args[0])
	vendor := v.getVendorInternal(ic.DAO, address)
	return stackitem.NewBool(vendor != nil && vendor.Active)
}

func (v *VTS) getVendorCategories(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	address := toUint160(args[0])
	vendor := v.getVendorInternal(ic.DAO, address)
	if vendor == nil {
		return stackitem.NewBigInteger(big.NewInt(0))
	}
	return stackitem.NewBigInteger(big.NewInt(int64(vendor.Categories)))
}

func (v *VTS) getVendorCountInternal(d *dao.Simple) int64 {
	si := d.GetStorageItem(v.ID, vtsVendorCountKey)
	if si == nil {
		return 0
	}
	return int64(binary.BigEndian.Uint64(si))
}

func (v *VTS) putVendorCount(d *dao.Simple, count int64) {
	data := make([]byte, 8)
	binary.BigEndian.PutUint64(data, uint64(count))
	d.PutStorageItem(v.ID, vtsVendorCountKey, data)
}

// ============ Spending Logic ============

func (v *VTS) spend(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	from := toUint160(args[0])
	vendor := toUint160(args[1])
	amount := toBigInt(args[2])
	data := args[3]

	if amount.Sign() <= 0 {
		panic("amount must be positive")
	}

	// Check witness for sender
	caller := ic.VM.GetCallingScriptHash()
	if !from.Equals(caller) {
		ok, err := checkWitness(ic, from)
		if err != nil || !ok {
			panic("invalid signature")
		}
	}

	// Check property rights via Lex (if Lex is wired)
	if v.Lex != nil && !v.Lex.CheckPropertyRight(ic.DAO, from, ic.Block.Index) {
		panic("property rights restricted")
	}

	// Verify vendor is registered and active
	vendorInfo := v.getVendorInternal(ic.DAO, vendor)
	if vendorInfo == nil || !vendorInfo.Active {
		panic("invalid or inactive vendor")
	}

	// Check age verification for age-restricted vendors (e.g., alcohol, tobacco)
	if vendorInfo.AgeRestricted {
		if !v.Vita.IsAdultVerified(ic.DAO, from) {
			panic("age verification required for this vendor")
		}
	}

	// Get sender's balance
	fromBal := v.getBalanceInternal(ic.DAO, from)

	// Try to spend from matching restricted categories first
	remaining := new(big.Int).Set(amount)
	var categoriesUsed uint8 = 0

	for category, catBal := range fromBal.Restricted {
		if remaining.Sign() <= 0 {
			break
		}
		// Check if vendor accepts this category
		if vendorInfo.Categories&category != 0 {
			toSpend := new(big.Int)
			if catBal.Cmp(remaining) >= 0 {
				toSpend.Set(remaining)
			} else {
				toSpend.Set(catBal)
			}
			catBal.Sub(catBal, toSpend)
			if catBal.Sign() == 0 {
				delete(fromBal.Restricted, category)
			}
			remaining.Sub(remaining, toSpend)
			categoriesUsed |= category
		}
	}

	// If still remaining, use unrestricted balance
	if remaining.Sign() > 0 {
		if fromBal.Unrestricted.Cmp(remaining) < 0 {
			panic("insufficient funds")
		}
		fromBal.Unrestricted.Sub(&fromBal.Unrestricted, remaining)
	}

	// Save sender balance
	v.putBalance(ic.DAO, from, fromBal)

	// Credit vendor with unrestricted balance
	vendorBal := v.getBalanceInternal(ic.DAO, vendor)
	vendorBal.Unrestricted.Add(&vendorBal.Unrestricted, amount)
	v.putBalance(ic.DAO, vendor, vendorBal)

	// Emit events
	v.emitTransfer(ic, &from, &vendor, amount)
	ic.AddNotification(v.Hash, "Spend", stackitem.NewArray([]stackitem.Item{
		stackitem.NewByteArray(from.BytesBE()),
		stackitem.NewByteArray(vendor.BytesBE()),
		stackitem.NewBigInteger(amount),
		stackitem.NewBigInteger(big.NewInt(int64(categoriesUsed))),
	}))

	_ = data // data parameter for compatibility
	return stackitem.NewBool(true)
}

func (v *VTS) canSpendAt(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	account := toUint160(args[0])
	vendor := toUint160(args[1])
	amount := toBigInt(args[2])

	if amount.Sign() <= 0 {
		return stackitem.NewBool(false)
	}

	// Check vendor
	vendorInfo := v.getVendorInternal(ic.DAO, vendor)
	if vendorInfo == nil || !vendorInfo.Active {
		return stackitem.NewBool(false)
	}

	// Check age verification for age-restricted vendors
	if vendorInfo.AgeRestricted {
		if !v.Vita.IsAdultVerified(ic.DAO, account) {
			return stackitem.NewBool(false)
		}
	}

	// Get balance
	bal := v.getBalanceInternal(ic.DAO, account)

	// Calculate available funds
	available := new(big.Int).Set(&bal.Unrestricted)
	for category, catBal := range bal.Restricted {
		if vendorInfo.Categories&category != 0 {
			available.Add(available, catBal)
		}
	}

	return stackitem.NewBool(available.Cmp(amount) >= 0)
}

// ============ Tax Accounting ============

func (v *VTS) payWage(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	employer := toUint160(args[0])
	employee := toUint160(args[1])
	grossAmount := toBigInt(args[2])
	taxRate := toUint64(args[3]) // basis points (e.g., 2500 = 25%)

	if grossAmount.Sign() <= 0 {
		panic("gross amount must be positive")
	}
	if taxRate > 10000 {
		panic("tax rate cannot exceed 100%")
	}

	// Check witness for employer
	caller := ic.VM.GetCallingScriptHash()
	if !employer.Equals(caller) {
		ok, err := checkWitness(ic, employer)
		if err != nil || !ok {
			panic("invalid signature")
		}
	}

	// Get employer balance
	employerBal := v.getBalanceInternal(ic.DAO, employer)
	if employerBal.Unrestricted.Cmp(grossAmount) < 0 {
		panic("insufficient funds")
	}

	// Calculate tax and net amounts
	taxAmount := new(big.Int).Mul(grossAmount, big.NewInt(int64(taxRate)))
	taxAmount.Div(taxAmount, big.NewInt(10000))
	netAmount := new(big.Int).Sub(grossAmount, taxAmount)

	// Get tax config for treasury address
	taxConfig := v.getTaxConfigInternal(ic.DAO)

	// Deduct from employer
	employerBal.Unrestricted.Sub(&employerBal.Unrestricted, grossAmount)
	v.putBalance(ic.DAO, employer, employerBal)

	// Credit employee (net)
	employeeBal := v.getBalanceInternal(ic.DAO, employee)
	employeeBal.Unrestricted.Add(&employeeBal.Unrestricted, netAmount)
	v.putBalance(ic.DAO, employee, employeeBal)

	// Credit treasury (tax)
	if taxAmount.Sign() > 0 && !taxConfig.TreasuryAddress.Equals(util.Uint160{}) {
		treasuryBal := v.getBalanceInternal(ic.DAO, taxConfig.TreasuryAddress)
		treasuryBal.Unrestricted.Add(&treasuryBal.Unrestricted, taxAmount)
		v.putBalance(ic.DAO, taxConfig.TreasuryAddress, treasuryBal)
	}

	// Store transaction record for tax reporting
	record := &state.TransactionRecord{
		TxHash:      ic.Tx.Hash(),
		BlockHeight: ic.BlockHeight(),
		From:        employer,
		To:          employee,
		Amount:      *grossAmount,
		TxType:      state.TxTypeIncome,
		Category:    state.CategoryUnrestricted,
		TaxWithheld: *taxAmount,
		TaxRate:     uint16(taxRate),
	}
	v.storeTransactionRecord(ic.DAO, record)

	// Emit events
	v.emitTransfer(ic, &employer, &employee, netAmount)
	if taxAmount.Sign() > 0 && !taxConfig.TreasuryAddress.Equals(util.Uint160{}) {
		v.emitTransfer(ic, &employer, &taxConfig.TreasuryAddress, taxAmount)
	}
	ic.AddNotification(v.Hash, "TaxWithheld", stackitem.NewArray([]stackitem.Item{
		stackitem.NewByteArray(employer.BytesBE()),
		stackitem.NewByteArray(employee.BytesBE()),
		stackitem.NewBigInteger(grossAmount),
		stackitem.NewBigInteger(taxAmount),
		stackitem.NewBigInteger(big.NewInt(int64(taxRate))),
	}))

	return stackitem.NewBool(true)
}

func (v *VTS) setTaxConfig(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	if !v.checkCommittee(ic) {
		panic("caller is not a committee member")
	}

	incomeRate := uint16(toUint64(args[0]))
	salesRate := uint16(toUint64(args[1]))
	treasuryAddress := toUint160(args[2])
	exemptCategories := toUint8(args[3])

	if incomeRate > 10000 || salesRate > 10000 {
		panic("rate cannot exceed 100%")
	}

	cfg := &state.TaxConfig{
		DefaultIncomeRate: incomeRate,
		DefaultSalesRate:  salesRate,
		TreasuryAddress:   treasuryAddress,
		ExemptCategories:  exemptCategories,
	}
	v.putTaxConfig(ic.DAO, cfg)

	return stackitem.NewBool(true)
}

func (v *VTS) getTaxConfig(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
	cfg := v.getTaxConfigInternal(ic.DAO)
	item, _ := cfg.ToStackItem()
	return item
}

func (v *VTS) getTaxConfigInternal(d *dao.Simple) *state.TaxConfig {
	si := d.GetStorageItem(v.ID, vtsTaxConfigKey)
	if si == nil {
		return &state.TaxConfig{}
	}

	item, err := stackitem.Deserialize(si)
	if err != nil {
		return &state.TaxConfig{}
	}

	cfg := &state.TaxConfig{}
	if err := cfg.FromStackItem(item); err != nil {
		return &state.TaxConfig{}
	}
	return cfg
}

func (v *VTS) putTaxConfig(d *dao.Simple, cfg *state.TaxConfig) {
	item, _ := cfg.ToStackItem()
	data, _ := stackitem.Serialize(item)
	d.PutStorageItem(v.ID, vtsTaxConfigKey, data)
}

func (v *VTS) issueTaxRefund(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	if !v.checkCommittee(ic) {
		panic("caller is not a committee member")
	}

	account := toUint160(args[0])
	amount := toBigInt(args[1])

	if amount.Sign() <= 0 {
		panic("amount must be positive")
	}

	// Mint new VTS as refund (comes from treasury/system)
	v.mintUnrestricted(ic, account, amount)

	ic.AddNotification(v.Hash, "TaxRefunded", stackitem.NewArray([]stackitem.Item{
		stackitem.NewByteArray(account.BytesBE()),
		stackitem.NewBigInteger(amount),
	}))

	return stackitem.NewBool(true)
}

// ============ Tax Reporting Methods ============

// makeTxRecordKey creates a storage key for a transaction record.
func (v *VTS) makeTxRecordKey(blockHeight uint32, txIndex uint32) []byte {
	key := make([]byte, 9)
	key[0] = vtsPrefixTxRecord
	binary.BigEndian.PutUint32(key[1:5], blockHeight)
	binary.BigEndian.PutUint32(key[5:9], txIndex)
	return key
}

// makeAccountTxIndexKey creates a storage key for an account's transaction index.
func (v *VTS) makeAccountTxIndexKey(account util.Uint160, blockHeight uint32) []byte {
	key := make([]byte, 25)
	key[0] = vtsPrefixAccountTxs
	copy(key[1:21], account.BytesBE())
	binary.BigEndian.PutUint32(key[21:25], blockHeight)
	return key
}

// makeTaxWithheldKey creates a storage key for cumulative tax withheld.
func (v *VTS) makeTaxWithheldKey(account util.Uint160, blockHeight uint32) []byte {
	key := make([]byte, 25)
	key[0] = vtsPrefixTaxWithheld
	copy(key[1:21], account.BytesBE())
	binary.BigEndian.PutUint32(key[21:25], blockHeight)
	return key
}

// storeTransactionRecord stores a transaction record and updates indexes.
func (v *VTS) storeTransactionRecord(d *dao.Simple, record *state.TransactionRecord) {
	// Get current tx index for this block
	txIndex := v.getNextTxIndex(d, record.BlockHeight)

	// Store the record
	key := v.makeTxRecordKey(record.BlockHeight, txIndex)
	item, _ := record.ToStackItem()
	data, err := stackitem.Serialize(item)
	if err != nil {
		panic("failed to serialize transaction record")
	}
	d.PutStorageItem(v.ID, key, data)

	// Update account indexes for both from and to
	v.addAccountTxIndex(d, record.From, record.BlockHeight, txIndex)
	if !record.To.Equals(record.From) {
		v.addAccountTxIndex(d, record.To, record.BlockHeight, txIndex)
	}

	// Update cumulative tax withheld for recipient (income tax)
	if record.TxType == state.TxTypeIncome && record.TaxWithheld.Sign() > 0 {
		v.addCumulativeTaxWithheld(d, record.To, record.BlockHeight, &record.TaxWithheld)
	}

	// Increment tx count for this block
	v.putTxCount(d, record.BlockHeight, txIndex+1)
}

// getNextTxIndex returns the next transaction index for a block.
func (v *VTS) getNextTxIndex(d *dao.Simple, blockHeight uint32) uint32 {
	return v.getTxCount(d, blockHeight)
}

// makeTxCountKey creates a storage key for transaction count per block.
func (v *VTS) makeTxCountKey(blockHeight uint32) []byte {
	key := make([]byte, 5)
	key[0] = vtsPrefixTxRecord
	binary.BigEndian.PutUint32(key[1:5], blockHeight)
	return key
}

// getTxCount returns the transaction count for a block.
func (v *VTS) getTxCount(d *dao.Simple, blockHeight uint32) uint32 {
	key := v.makeTxCountKey(blockHeight)
	si := d.GetStorageItem(v.ID, key)
	if si == nil || len(si) < 4 {
		return 0
	}
	return binary.BigEndian.Uint32(si)
}

// putTxCount stores the transaction count for a block.
func (v *VTS) putTxCount(d *dao.Simple, blockHeight uint32, count uint32) {
	key := v.makeTxCountKey(blockHeight)
	data := make([]byte, 4)
	binary.BigEndian.PutUint32(data, count)
	d.PutStorageItem(v.ID, key, data)
}

// addAccountTxIndex adds a transaction index to an account's list.
func (v *VTS) addAccountTxIndex(d *dao.Simple, account util.Uint160, blockHeight uint32, txIndex uint32) {
	key := v.makeAccountTxIndexKey(account, blockHeight)
	existing := d.GetStorageItem(v.ID, key)

	// Append new index
	newData := make([]byte, len(existing)+4)
	copy(newData, existing)
	binary.BigEndian.PutUint32(newData[len(existing):], txIndex)
	d.PutStorageItem(v.ID, key, newData)
}

// addCumulativeTaxWithheld adds to the cumulative tax withheld for an account.
func (v *VTS) addCumulativeTaxWithheld(d *dao.Simple, account util.Uint160, blockHeight uint32, amount *big.Int) {
	key := v.makeTaxWithheldKey(account, blockHeight)
	existing := d.GetStorageItem(v.ID, key)

	cumulative := big.NewInt(0)
	if len(existing) > 0 {
		cumulative.SetBytes(existing)
	}
	cumulative.Add(cumulative, amount)

	d.PutStorageItem(v.ID, key, cumulative.Bytes())
}

// getTransactions returns transactions for an account in a block range.
func (v *VTS) getTransactions(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	account := toUint160(args[0])
	startBlock := uint32(toUint64(args[1]))
	endBlock := uint32(toUint64(args[2]))

	if endBlock < startBlock {
		panic("endBlock must be >= startBlock")
	}

	// Limit range to prevent DoS
	maxRange := uint32(10000)
	if endBlock-startBlock > maxRange {
		panic("block range too large")
	}

	var records []stackitem.Item
	for block := startBlock; block <= endBlock; block++ {
		// Get transaction indexes for this account at this block
		key := v.makeAccountTxIndexKey(account, block)
		indexData := ic.DAO.GetStorageItem(v.ID, key)

		// Parse indexes
		for i := 0; i+4 <= len(indexData); i += 4 {
			txIndex := binary.BigEndian.Uint32(indexData[i : i+4])

			// Get the transaction record
			recordKey := v.makeTxRecordKey(block, txIndex)
			recordData := ic.DAO.GetStorageItem(v.ID, recordKey)
			if len(recordData) > 0 {
				item, err := stackitem.Deserialize(recordData)
				if err == nil {
					records = append(records, item)
				}
			}
		}
	}

	return stackitem.NewArray(records)
}

// getIncomeForPeriod returns total taxable income for an account in a block range.
func (v *VTS) getIncomeForPeriod(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	account := toUint160(args[0])
	startBlock := uint32(toUint64(args[1]))
	endBlock := uint32(toUint64(args[2]))

	if endBlock < startBlock {
		panic("endBlock must be >= startBlock")
	}

	totalIncome := big.NewInt(0)
	for block := startBlock; block <= endBlock; block++ {
		key := v.makeAccountTxIndexKey(account, block)
		indexData := ic.DAO.GetStorageItem(v.ID, key)

		for i := 0; i+4 <= len(indexData); i += 4 {
			txIndex := binary.BigEndian.Uint32(indexData[i : i+4])
			recordKey := v.makeTxRecordKey(block, txIndex)
			recordData := ic.DAO.GetStorageItem(v.ID, recordKey)

			if len(recordData) > 0 {
				item, err := stackitem.Deserialize(recordData)
				if err == nil {
					var record state.TransactionRecord
					if record.FromStackItem(item) == nil {
						// Only count income where this account is the recipient
						if record.To.Equals(account) && record.TxType == state.TxTypeIncome {
							totalIncome.Add(totalIncome, &record.Amount)
						}
					}
				}
			}
		}
	}

	return stackitem.NewBigInteger(totalIncome)
}

// getTaxWithheld returns total tax withheld for an account in a block range.
func (v *VTS) getTaxWithheld(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	account := toUint160(args[0])
	startBlock := uint32(toUint64(args[1]))
	endBlock := uint32(toUint64(args[2]))

	if endBlock < startBlock {
		panic("endBlock must be >= startBlock")
	}

	totalTax := big.NewInt(0)
	for block := startBlock; block <= endBlock; block++ {
		key := v.makeTaxWithheldKey(account, block)
		data := ic.DAO.GetStorageItem(v.ID, key)
		if len(data) > 0 {
			blockTax := new(big.Int).SetBytes(data)
			totalTax.Add(totalTax, blockTax)
		}
	}

	return stackitem.NewBigInteger(totalTax)
}

// getDeductibleExpenses returns total deductible expenses for an account in a block range.
func (v *VTS) getDeductibleExpenses(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	account := toUint160(args[0])
	startBlock := uint32(toUint64(args[1]))
	endBlock := uint32(toUint64(args[2]))
	category := toUint8(args[3])

	if endBlock < startBlock {
		panic("endBlock must be >= startBlock")
	}

	totalExpenses := big.NewInt(0)
	for block := startBlock; block <= endBlock; block++ {
		key := v.makeAccountTxIndexKey(account, block)
		indexData := ic.DAO.GetStorageItem(v.ID, key)

		for i := 0; i+4 <= len(indexData); i += 4 {
			txIndex := binary.BigEndian.Uint32(indexData[i : i+4])
			recordKey := v.makeTxRecordKey(block, txIndex)
			recordData := ic.DAO.GetStorageItem(v.ID, recordKey)

			if len(recordData) > 0 {
				item, err := stackitem.Deserialize(recordData)
				if err == nil {
					var record state.TransactionRecord
					if record.FromStackItem(item) == nil {
						// Count expenses where this account is the sender
						if record.From.Equals(account) && record.TxType == state.TxTypeExpense {
							// Filter by category if specified
							if category == 0 || record.Category == category {
								totalExpenses.Add(totalExpenses, &record.Amount)
							}
						}
					}
				}
			}
		}
	}

	return stackitem.NewBigInteger(totalExpenses)
}

// getTaxSummary returns a tax summary for an account in a block range.
func (v *VTS) getTaxSummary(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	account := toUint160(args[0])
	startBlock := uint32(toUint64(args[1]))
	endBlock := uint32(toUint64(args[2]))

	if endBlock < startBlock {
		panic("endBlock must be >= startBlock")
	}

	summary := &state.TaxSummary{
		Account:    account,
		StartBlock: startBlock,
		EndBlock:   endBlock,
	}

	// Iterate through all transactions in the period
	for block := startBlock; block <= endBlock; block++ {
		// Get cumulative tax for this block
		taxKey := v.makeTaxWithheldKey(account, block)
		taxData := ic.DAO.GetStorageItem(v.ID, taxKey)
		if len(taxData) > 0 {
			blockTax := new(big.Int).SetBytes(taxData)
			summary.TaxWithheld.Add(&summary.TaxWithheld, blockTax)
		}

		// Get transaction details
		key := v.makeAccountTxIndexKey(account, block)
		indexData := ic.DAO.GetStorageItem(v.ID, key)

		for i := 0; i+4 <= len(indexData); i += 4 {
			txIndex := binary.BigEndian.Uint32(indexData[i : i+4])
			recordKey := v.makeTxRecordKey(block, txIndex)
			recordData := ic.DAO.GetStorageItem(v.ID, recordKey)

			if len(recordData) > 0 {
				item, err := stackitem.Deserialize(recordData)
				if err == nil {
					var record state.TransactionRecord
					if record.FromStackItem(item) == nil {
						if record.To.Equals(account) {
							switch record.TxType {
							case state.TxTypeIncome:
								summary.TotalIncome.Add(&summary.TotalIncome, &record.Amount)
							case state.TxTypeBenefit:
								summary.TotalBenefits.Add(&summary.TotalBenefits, &record.Amount)
							}
						}
						if record.From.Equals(account) && record.TxType == state.TxTypeExpense {
							summary.TotalExpenses.Add(&summary.TotalExpenses, &record.Amount)
							// Medical and Education expenses are typically deductible
							if record.Category == state.CategoryMedical || record.Category == state.CategoryEducation {
								summary.DeductibleExpenses.Add(&summary.DeductibleExpenses, &record.Amount)
							}
						}
					}
				}
			}
		}
	}

	// Calculate estimated tax owed (simple flat rate calculation)
	taxConfig := v.getTaxConfigInternal(ic.DAO)
	if taxConfig.DefaultIncomeRate > 0 {
		taxOwed := new(big.Int).Mul(&summary.TotalIncome, big.NewInt(int64(taxConfig.DefaultIncomeRate)))
		taxOwed.Div(taxOwed, big.NewInt(10000))
		summary.EstimatedOwed = *taxOwed
	}

	// Balance = TaxWithheld - EstimatedOwed (positive = refund due)
	summary.Balance.Sub(&summary.TaxWithheld, &summary.EstimatedOwed)

	item, _ := summary.ToStackItem()
	return item
}

// ============ Public Internal Methods for Cross-Contract Use ============

// BalanceOf returns VTS balance for the account.
func (v *VTS) BalanceOf(d *dao.Simple, acc util.Uint160) *big.Int {
	bal := v.getBalanceInternal(d, acc)
	return bal.Total()
}

// UnrestrictedBalanceOf returns unrestricted VTS balance for the account.
func (v *VTS) UnrestrictedBalanceOf(d *dao.Simple, acc util.Uint160) *big.Int {
	bal := v.getBalanceInternal(d, acc)
	return &bal.Unrestricted
}

// Mint mints unrestricted VTS to the account.
func (v *VTS) Mint(ic *interop.Context, to util.Uint160, amount *big.Int) {
	v.mintUnrestricted(ic, to, amount)
}

// MintRestricted mints restricted VTS to the account.
func (v *VTS) MintRestricted(ic *interop.Context, to util.Uint160, amount *big.Int, category uint8) {
	v.mintRestrictedInternal(ic, to, amount, category)
}

// Burn burns unrestricted VTS from the account.
func (v *VTS) Burn(ic *interop.Context, from util.Uint160, amount *big.Int) {
	bal := v.getBalanceInternal(ic.DAO, from)
	if bal.Unrestricted.Cmp(amount) < 0 {
		panic("insufficient funds")
	}
	bal.Unrestricted.Sub(&bal.Unrestricted, amount)
	v.putBalance(ic.DAO, from, bal)

	supply := v.getTotalSupplyInternal(ic.DAO)
	supply.Sub(supply, amount)
	v.putTotalSupply(ic.DAO, supply)

	v.emitTransfer(ic, &from, nil, amount)
}

// IsVendor returns true if the address is a registered active vendor.
func (v *VTS) IsVendor(d *dao.Simple, addr util.Uint160) bool {
	vendor := v.getVendorInternal(d, addr)
	return vendor != nil && vendor.Active
}

// ============ Helper Functions ============

func checkWitness(ic *interop.Context, h util.Uint160) (bool, error) {
	return runtime.CheckHashedWitness(ic, h)
}
