package native_test

import (
	"testing"
	"time"

	"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
	"github.com/stretchr/testify/require"
	"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/tutustest"
	"git.marketally.com/tutus-one/tutus-chain/pkg/vm/stackitem"
)

func newVTSClient(t *testing.T) *tutustest.ContractInvoker {
	return newNativeClient(t, nativenames.VTS)
}

// registerVitaForVTS is a helper to register a Vita for an account.
// This is required for accounts receiving restricted VTS.
// Uses the existing executor to ensure we're on the same blockchain.
func registerVitaForVTS(t *testing.T, e *tutustest.Executor, acc tutustest.Signer) {
	vitaHash := e.NativeHash(t, nativenames.Vita)
	c := e.NewInvoker(vitaHash, acc)
	owner := acc.ScriptHash()
	personHash := hash.Sha256(owner.BytesBE()).BytesBE()
	isEntity := false
	recoveryHash := hash.Sha256([]byte("recovery")).BytesBE()
	birthTimestamp := time.Now().Unix() - 30*365*24*60*60 // Default: 30 years old

	c.InvokeAndCheck(t, func(t testing.TB, stack []stackitem.Item) {
		require.Equal(t, 1, len(stack))
		_, ok := stack[0].Value().([]byte)
		require.True(t, ok, "expected ByteArray result for tokenID")
	}, "register", owner.BytesBE(), personHash, isEntity, recoveryHash, birthTimestamp)
}

// addAgeVerifiedAttribute adds the "age_verified" attribute to an account's Vita.
// This is required for spending at age-restricted vendors.
// Uses the existing executor to ensure we're on the same blockchain.
func addAgeVerifiedAttribute(t *testing.T, e *tutustest.Executor, committee tutustest.Signer, acc tutustest.Signer) {
	vitaHash := e.NativeHash(t, nativenames.Vita)
	c := e.CommitteeInvoker(vitaHash)
	owner := acc.ScriptHash()

	// Get the token ID from getToken result
	stack, err := c.TestInvoke(t, "getToken", owner.BytesBE())
	require.NoError(t, err)
	tokenItem := stack.Pop().Item()
	tokenArr := tokenItem.Value().([]stackitem.Item)
	tokenIDBI, _ := tokenArr[0].TryInteger()
	tokenID := tokenIDBI.Int64()

	// Create hash and encrypted value for the attribute
	valueHash := hash.Sha256([]byte("true")).BytesBE()
	valueEnc := []byte("true") // For testing, we just use plaintext

	// setAttribute: tokenId, key, valueHash, valueEnc, expiresAt, disclosureLevel
	// disclosureLevel: 0 = private, 1 = selective, 2 = public
	c.Invoke(t, true, "setAttribute",
		tokenID,
		"age_verified",
		valueHash,
		valueEnc,
		int64(0), // expiresAt (0 = never expires)
		int64(2), // disclosureLevel = public
	)
}

// TestVTS_NEP17Compliance tests basic NEP-17 methods.
func TestVTS_NEP17Compliance(t *testing.T) {
	c := newVTSClient(t)

	t.Run("symbol", func(t *testing.T) {
		c.Invoke(t, "VTS", "symbol")
	})

	t.Run("decimals", func(t *testing.T) {
		c.Invoke(t, 8, "decimals")
	})

	t.Run("totalSupply initial", func(t *testing.T) {
		c.Invoke(t, 0, "totalSupply")
	})

	t.Run("balanceOf empty account", func(t *testing.T) {
		acc := c.NewAccount(t)
		c.Invoke(t, 0, "balanceOf", acc.ScriptHash())
	})
}

// TestVTS_Mint tests minting unrestricted VTS.
func TestVTS_Mint(t *testing.T) {
	c := newVTSClient(t)
	e := c.Executor

	acc := e.NewAccount(t)
	committeeInvoker := c.WithSigners(c.Committee)
	userInvoker := c.WithSigners(acc)

	t.Run("non-committee cannot mint", func(t *testing.T) {
		userInvoker.InvokeFail(t, "caller is not a committee member", "mint", acc.ScriptHash(), 1000_00000000)
	})

	t.Run("committee can mint", func(t *testing.T) {
		committeeInvoker.Invoke(t, true, "mint", acc.ScriptHash(), 1000_00000000)
	})

	t.Run("balance updated", func(t *testing.T) {
		c.Invoke(t, 1000_00000000, "balanceOf", acc.ScriptHash())
	})

	t.Run("unrestricted balance updated", func(t *testing.T) {
		c.Invoke(t, 1000_00000000, "unrestrictedBalanceOf", acc.ScriptHash())
	})

	t.Run("totalSupply updated", func(t *testing.T) {
		c.Invoke(t, 1000_00000000, "totalSupply")
	})
}

// TestVTS_MintRestricted tests minting restricted VTS.
func TestVTS_MintRestricted(t *testing.T) {
	c := newVTSClient(t)
	e := c.Executor

	acc := e.NewAccount(t)
	committeeInvoker := c.WithSigners(c.Committee)

	// Restricted VTS requires recipient to have a Vita
	registerVitaForVTS(t, e, acc)

	t.Run("mint food-restricted VTS", func(t *testing.T) {
		committeeInvoker.Invoke(t, true, "mintRestricted", acc.ScriptHash(), 500_00000000, state.CategoryFood)
	})

	t.Run("total balance includes restricted", func(t *testing.T) {
		c.Invoke(t, 500_00000000, "balanceOf", acc.ScriptHash())
	})

	t.Run("unrestricted is still zero", func(t *testing.T) {
		c.Invoke(t, 0, "unrestrictedBalanceOf", acc.ScriptHash())
	})

	t.Run("restricted balance correct", func(t *testing.T) {
		c.Invoke(t, 500_00000000, "restrictedBalanceOf", acc.ScriptHash(), state.CategoryFood)
	})

	t.Run("other restricted category is zero", func(t *testing.T) {
		c.Invoke(t, 0, "restrictedBalanceOf", acc.ScriptHash(), state.CategoryShelter)
	})
}

// TestVTS_Burn tests burning unrestricted VTS.
func TestVTS_Burn(t *testing.T) {
	c := newVTSClient(t)
	e := c.Executor

	acc := e.NewAccount(t)
	committeeInvoker := c.WithSigners(c.Committee)
	userInvoker := c.WithSigners(acc)

	// Mint first
	committeeInvoker.Invoke(t, true, "mint", acc.ScriptHash(), 1000_00000000)

	t.Run("non-committee cannot burn", func(t *testing.T) {
		userInvoker.InvokeFail(t, "caller is not a committee member", "burn", acc.ScriptHash(), 100_00000000)
	})

	t.Run("committee can burn", func(t *testing.T) {
		committeeInvoker.Invoke(t, true, "burn", acc.ScriptHash(), 100_00000000)
	})

	t.Run("balance reduced", func(t *testing.T) {
		c.Invoke(t, 900_00000000, "balanceOf", acc.ScriptHash())
	})

	t.Run("totalSupply reduced", func(t *testing.T) {
		c.Invoke(t, 900_00000000, "totalSupply")
	})
}

// TestVTS_Transfer tests transferring unrestricted VTS.
func TestVTS_Transfer(t *testing.T) {
	c := newVTSClient(t)
	e := c.Executor

	sender := e.NewAccount(t)
	receiver := e.NewAccount(t)
	committeeInvoker := c.WithSigners(c.Committee)
	senderInvoker := c.WithSigners(sender)

	// Setup: Register Vita for sender (required for property rights/transfers)
	registerVitaForVTS(t, e, sender)

	// Mint to sender
	committeeInvoker.Invoke(t, true, "mint", sender.ScriptHash(), 1000_00000000)

	t.Run("transfer without signature fails", func(t *testing.T) {
		// Transfer returns false (not panic) when signature is missing
		c.Invoke(t, false, "transfer", sender.ScriptHash(), receiver.ScriptHash(), 100_00000000, nil)
	})

	t.Run("transfer with signature succeeds", func(t *testing.T) {
		senderInvoker.Invoke(t, true, "transfer", sender.ScriptHash(), receiver.ScriptHash(), 100_00000000, nil)
	})

	t.Run("sender balance reduced", func(t *testing.T) {
		c.Invoke(t, 900_00000000, "balanceOf", sender.ScriptHash())
	})

	t.Run("receiver balance increased", func(t *testing.T) {
		c.Invoke(t, 100_00000000, "balanceOf", receiver.ScriptHash())
	})

	t.Run("cannot transfer more than balance", func(t *testing.T) {
		senderInvoker.Invoke(t, false, "transfer", sender.ScriptHash(), receiver.ScriptHash(), 10000_00000000, nil)
	})

	t.Run("cannot transfer negative", func(t *testing.T) {
		// Transfer returns false (not panic) for negative amounts
		senderInvoker.Invoke(t, false, "transfer", sender.ScriptHash(), receiver.ScriptHash(), -1, nil)
	})
}

// TestVTS_VendorRegistration tests vendor management.
func TestVTS_VendorRegistration(t *testing.T) {
	c := newVTSClient(t)
	e := c.Executor

	vendorAcc := e.NewAccount(t)
	userAcc := e.NewAccount(t)
	committeeInvoker := c.WithSigners(c.Committee)
	userInvoker := c.WithSigners(userAcc)

	t.Run("non-committee cannot register vendor", func(t *testing.T) {
		userInvoker.InvokeFail(t, "caller is not a committee member", "registerVendor", vendorAcc.ScriptHash(), "Test Store", state.CategoryFood, false)
	})

	t.Run("committee can register vendor", func(t *testing.T) {
		committeeInvoker.Invoke(t, true, "registerVendor", vendorAcc.ScriptHash(), "Test Store", state.CategoryFood, false)
	})

	t.Run("isVendor returns true", func(t *testing.T) {
		c.Invoke(t, true, "isVendor", vendorAcc.ScriptHash())
	})

	t.Run("non-vendor returns false", func(t *testing.T) {
		c.Invoke(t, false, "isVendor", userAcc.ScriptHash())
	})

	t.Run("getVendorCategories", func(t *testing.T) {
		c.Invoke(t, int64(state.CategoryFood), "getVendorCategories", vendorAcc.ScriptHash())
	})

	t.Run("getVendor returns vendor info", func(t *testing.T) {
		c.InvokeAndCheck(t, func(t testing.TB, stack []stackitem.Item) {
			require.Equal(t, 1, len(stack))
			arr, ok := stack[0].Value().([]stackitem.Item)
			require.True(t, ok, "expected array")
			require.Equal(t, 7, len(arr)) // Vendor struct has 7 fields (including AgeRestricted)
		}, "getVendor", vendorAcc.ScriptHash())
	})

	t.Run("update vendor categories", func(t *testing.T) {
		newCategories := state.CategoryFood | state.CategoryShelter
		committeeInvoker.Invoke(t, true, "updateVendor", vendorAcc.ScriptHash(), "Test Store Updated", newCategories, false)
		c.Invoke(t, int64(newCategories), "getVendorCategories", vendorAcc.ScriptHash())
	})

	t.Run("deactivate vendor", func(t *testing.T) {
		committeeInvoker.Invoke(t, true, "deactivateVendor", vendorAcc.ScriptHash())
		// isVendor should still return true (vendor exists) but Active=false
		c.Invoke(t, false, "isVendor", vendorAcc.ScriptHash())
	})
}

// TestVTS_Spend tests spending at vendors.
func TestVTS_Spend(t *testing.T) {
	c := newVTSClient(t)
	e := c.Executor

	customer := e.NewAccount(t)
	foodVendor := e.NewAccount(t)
	committeeInvoker := c.WithSigners(c.Committee)
	customerInvoker := c.WithSigners(customer)

	// Setup: Register Vita for customer (required for property rights/spending)
	registerVitaForVTS(t, e, customer)

	// Setup: Register food vendor and mint to customer
	committeeInvoker.Invoke(t, true, "registerVendor", foodVendor.ScriptHash(), "Food Store", state.CategoryFood, false)
	committeeInvoker.Invoke(t, true, "mint", customer.ScriptHash(), 300_00000000)

	t.Run("customer can spend at vendor", func(t *testing.T) {
		customerInvoker.Invoke(t, true, "spend", customer.ScriptHash(), foodVendor.ScriptHash(), 50_00000000, nil)
	})

	t.Run("vendor received VTS", func(t *testing.T) {
		c.Invoke(t, 50_00000000, "balanceOf", foodVendor.ScriptHash())
	})

	t.Run("customer balance reduced", func(t *testing.T) {
		c.Invoke(t, 250_00000000, "balanceOf", customer.ScriptHash())
	})

	t.Run("cannot spend at non-vendor", func(t *testing.T) {
		nonVendor := e.NewAccount(t)
		customerInvoker.InvokeFail(t, "invalid or inactive vendor", "spend", customer.ScriptHash(), nonVendor.ScriptHash(), 10_00000000, nil)
	})
}

// TestVTS_CanSpendAt tests the canSpendAt query method.
func TestVTS_CanSpendAt(t *testing.T) {
	c := newVTSClient(t)
	e := c.Executor

	customer := e.NewAccount(t)
	vendor := e.NewAccount(t)
	committeeInvoker := c.WithSigners(c.Committee)

	// Setup: Register Vita for customer (required for property rights)
	registerVitaForVTS(t, e, customer)

	// Setup
	committeeInvoker.Invoke(t, true, "registerVendor", vendor.ScriptHash(), "Store", state.CategoryFood, false)
	committeeInvoker.Invoke(t, true, "mint", customer.ScriptHash(), 100_00000000)

	t.Run("can spend within balance", func(t *testing.T) {
		c.Invoke(t, true, "canSpendAt", customer.ScriptHash(), vendor.ScriptHash(), 50_00000000)
	})

	t.Run("cannot spend more than balance", func(t *testing.T) {
		c.Invoke(t, false, "canSpendAt", customer.ScriptHash(), vendor.ScriptHash(), 200_00000000)
	})
}

// TestVTS_ConvertToUnrestricted tests converting restricted VTS to unrestricted.
func TestVTS_ConvertToUnrestricted(t *testing.T) {
	c := newVTSClient(t)
	e := c.Executor

	acc := e.NewAccount(t)
	committeeInvoker := c.WithSigners(c.Committee)
	userInvoker := c.WithSigners(acc)

	// Restricted VTS requires Vita
	registerVitaForVTS(t, e, acc)

	// Mint restricted VTS
	committeeInvoker.Invoke(t, true, "mintRestricted", acc.ScriptHash(), 500_00000000, state.CategoryFood)

	t.Run("non-committee cannot convert", func(t *testing.T) {
		userInvoker.InvokeFail(t, "caller is not a committee member", "convertToUnrestricted", acc.ScriptHash(), state.CategoryFood, 100_00000000)
	})

	t.Run("committee can convert", func(t *testing.T) {
		committeeInvoker.Invoke(t, true, "convertToUnrestricted", acc.ScriptHash(), state.CategoryFood, 100_00000000)
	})

	t.Run("restricted balance reduced", func(t *testing.T) {
		c.Invoke(t, 400_00000000, "restrictedBalanceOf", acc.ScriptHash(), state.CategoryFood)
	})

	t.Run("unrestricted balance increased", func(t *testing.T) {
		c.Invoke(t, 100_00000000, "unrestrictedBalanceOf", acc.ScriptHash())
	})

	t.Run("total balance unchanged", func(t *testing.T) {
		c.Invoke(t, 500_00000000, "balanceOf", acc.ScriptHash())
	})
}

// TestVTS_TaxConfig tests tax configuration.
func TestVTS_TaxConfig(t *testing.T) {
	c := newVTSClient(t)
	e := c.Executor

	treasuryAcc := e.NewAccount(t)
	userAcc := e.NewAccount(t)
	committeeInvoker := c.WithSigners(c.Committee)
	userInvoker := c.WithSigners(userAcc)

	t.Run("non-committee cannot set tax config", func(t *testing.T) {
		userInvoker.InvokeFail(t, "caller is not a committee member", "setTaxConfig", 2500, 500, treasuryAcc.ScriptHash(), state.CategoryFood)
	})

	t.Run("committee can set tax config", func(t *testing.T) {
		// 25% income tax, 5% sales tax
		committeeInvoker.Invoke(t, true, "setTaxConfig", 2500, 500, treasuryAcc.ScriptHash(), state.CategoryFood)
	})

	t.Run("get tax config", func(t *testing.T) {
		c.InvokeAndCheck(t, func(t testing.TB, stack []stackitem.Item) {
			require.Equal(t, 1, len(stack))
			arr, ok := stack[0].Value().([]stackitem.Item)
			require.True(t, ok, "expected array")
			require.Equal(t, 4, len(arr)) // TaxConfig has 4 fields

			incomeRate, _ := arr[0].TryInteger()
			require.Equal(t, int64(2500), incomeRate.Int64())
		}, "getTaxConfig")
	})
}

// TestVTS_PayWage tests payroll with automatic tax withholding.
func TestVTS_PayWage(t *testing.T) {
	c := newVTSClient(t)
	e := c.Executor

	employer := e.NewAccount(t)
	employee := e.NewAccount(t)
	treasury := e.NewAccount(t)
	committeeInvoker := c.WithSigners(c.Committee)
	employerInvoker := c.WithSigners(employer)

	// Setup: Register Vita for employer (required for property rights/transfers)
	registerVitaForVTS(t, e, employer)

	// Setup: Set tax config and fund employer
	committeeInvoker.Invoke(t, true, "setTaxConfig", 2500, 0, treasury.ScriptHash(), 0) // 25% income tax
	committeeInvoker.Invoke(t, true, "mint", employer.ScriptHash(), 10000_00000000)

	t.Run("payWage with tax withholding", func(t *testing.T) {
		// Pay $5000 gross, 25% tax rate
		employerInvoker.Invoke(t, true, "payWage", employer.ScriptHash(), employee.ScriptHash(), 5000_00000000, 2500)
	})

	t.Run("employer balance reduced by gross", func(t *testing.T) {
		c.Invoke(t, 5000_00000000, "balanceOf", employer.ScriptHash())
	})

	t.Run("employee receives net (75%)", func(t *testing.T) {
		c.Invoke(t, 3750_00000000, "balanceOf", employee.ScriptHash())
	})

	t.Run("treasury receives tax (25%)", func(t *testing.T) {
		c.Invoke(t, 1250_00000000, "balanceOf", treasury.ScriptHash())
	})
}

// TestVTS_TaxRefund tests issuing tax refunds.
func TestVTS_TaxRefund(t *testing.T) {
	c := newVTSClient(t)
	e := c.Executor

	acc := e.NewAccount(t)
	treasury := e.NewAccount(t)
	committeeInvoker := c.WithSigners(c.Committee)
	userInvoker := c.WithSigners(acc)

	// Setup: Set tax config
	committeeInvoker.Invoke(t, true, "setTaxConfig", 2500, 0, treasury.ScriptHash(), 0)

	t.Run("non-committee cannot issue refund", func(t *testing.T) {
		userInvoker.InvokeFail(t, "caller is not a committee member", "issueTaxRefund", acc.ScriptHash(), 100_00000000)
	})

	t.Run("committee can issue refund", func(t *testing.T) {
		committeeInvoker.Invoke(t, true, "issueTaxRefund", acc.ScriptHash(), 100_00000000)
	})

	t.Run("refund credited to account", func(t *testing.T) {
		c.Invoke(t, 100_00000000, "balanceOf", acc.ScriptHash())
	})
}

// TestVTS_BalanceDetails tests the balanceDetails query.
func TestVTS_BalanceDetails(t *testing.T) {
	c := newVTSClient(t)
	e := c.Executor

	acc := e.NewAccount(t)
	committeeInvoker := c.WithSigners(c.Committee)

	// Restricted VTS requires Vita
	registerVitaForVTS(t, e, acc)

	// Mint mixed VTS
	committeeInvoker.Invoke(t, true, "mint", acc.ScriptHash(), 100_00000000)
	committeeInvoker.Invoke(t, true, "mintRestricted", acc.ScriptHash(), 200_00000000, state.CategoryFood)
	committeeInvoker.Invoke(t, true, "mintRestricted", acc.ScriptHash(), 300_00000000, state.CategoryShelter)

	t.Run("balanceDetails returns all categories", func(t *testing.T) {
		c.InvokeAndCheck(t, func(t testing.TB, stack []stackitem.Item) {
			require.Equal(t, 1, len(stack))
			arr, ok := stack[0].Value().([]stackitem.Item)
			require.True(t, ok, "expected array")
			// Should have unrestricted + categories with balances
			require.GreaterOrEqual(t, len(arr), 1)
		}, "balanceDetails", acc.ScriptHash())
	})
}

// TestVTS_TaxReportingMethods tests the tax reporting query methods.
func TestVTS_TaxReportingMethods(t *testing.T) {
	c := newVTSClient(t)
	e := c.Executor

	acc := e.NewAccount(t)

	// Block range for queries
	startBlock := int64(0)
	endBlock := int64(1000)

	t.Run("getTransactions returns array", func(t *testing.T) {
		c.InvokeAndCheck(t, func(t testing.TB, stack []stackitem.Item) {
			require.Equal(t, 1, len(stack))
			_, ok := stack[0].Value().([]stackitem.Item)
			require.True(t, ok, "expected array")
		}, "getTransactions", acc.ScriptHash(), startBlock, endBlock)
	})

	t.Run("getIncomeForPeriod returns integer", func(t *testing.T) {
		c.InvokeAndCheck(t, func(t testing.TB, stack []stackitem.Item) {
			require.Equal(t, 1, len(stack))
			_, err := stack[0].TryInteger()
			require.NoError(t, err)
		}, "getIncomeForPeriod", acc.ScriptHash(), startBlock, endBlock)
	})

	t.Run("getTaxWithheld returns integer", func(t *testing.T) {
		c.InvokeAndCheck(t, func(t testing.TB, stack []stackitem.Item) {
			require.Equal(t, 1, len(stack))
			_, err := stack[0].TryInteger()
			require.NoError(t, err)
		}, "getTaxWithheld", acc.ScriptHash(), startBlock, endBlock)
	})

	t.Run("getDeductibleExpenses returns integer", func(t *testing.T) {
		c.InvokeAndCheck(t, func(t testing.TB, stack []stackitem.Item) {
			require.Equal(t, 1, len(stack))
			_, err := stack[0].TryInteger()
			require.NoError(t, err)
		}, "getDeductibleExpenses", acc.ScriptHash(), startBlock, endBlock, 0)
	})

	t.Run("getTaxSummary returns array", func(t *testing.T) {
		c.InvokeAndCheck(t, func(t testing.TB, stack []stackitem.Item) {
			require.Equal(t, 1, len(stack))
			arr, ok := stack[0].Value().([]stackitem.Item)
			require.True(t, ok, "expected array for tax summary")
			// TaxSummary has 10 fields: Account, StartBlock, EndBlock, TotalIncome,
			// TotalBenefits, TotalExpenses, DeductibleExpenses, TaxWithheld, EstimatedOwed, Balance
			require.Equal(t, 10, len(arr))
		}, "getTaxSummary", acc.ScriptHash(), startBlock, endBlock)
	})
}

// TestVTS_NegativeAmount tests that negative amounts are rejected.
func TestVTS_NegativeAmount(t *testing.T) {
	c := newVTSClient(t)
	e := c.Executor

	acc := e.NewAccount(t)
	committeeInvoker := c.WithSigners(c.Committee)

	// Restricted VTS requires Vita
	registerVitaForVTS(t, e, acc)

	t.Run("mint negative fails", func(t *testing.T) {
		committeeInvoker.InvokeFail(t, "amount must be positive", "mint", acc.ScriptHash(), -1)
	})

	t.Run("mintRestricted negative fails", func(t *testing.T) {
		committeeInvoker.InvokeFail(t, "amount must be positive", "mintRestricted", acc.ScriptHash(), -1, state.CategoryFood)
	})
}

// TestVTS_MultiCategoryVendor tests vendors accepting multiple categories.
func TestVTS_MultiCategoryVendor(t *testing.T) {
	c := newVTSClient(t)
	e := c.Executor

	customer := e.NewAccount(t)
	generalStore := e.NewAccount(t)
	committeeInvoker := c.WithSigners(c.Committee)
	customerInvoker := c.WithSigners(customer)

	// Restricted VTS requires Vita
	registerVitaForVTS(t, e, customer)

	// Register vendor accepting food AND shelter
	categories := state.CategoryFood | state.CategoryShelter
	committeeInvoker.Invoke(t, true, "registerVendor", generalStore.ScriptHash(), "General Store", categories, false)

	// Mint restricted VTS for different categories
	committeeInvoker.Invoke(t, true, "mintRestricted", customer.ScriptHash(), 100_00000000, state.CategoryFood)
	committeeInvoker.Invoke(t, true, "mintRestricted", customer.ScriptHash(), 100_00000000, state.CategoryShelter)

	t.Run("can spend at multi-category vendor", func(t *testing.T) {
		customerInvoker.Invoke(t, true, "spend", customer.ScriptHash(), generalStore.ScriptHash(), 50_00000000, nil)
		// Total balance should be reduced by 50
		c.Invoke(t, 150_00000000, "balanceOf", customer.ScriptHash())
	})

	t.Run("can spend more at same vendor", func(t *testing.T) {
		customerInvoker.Invoke(t, true, "spend", customer.ScriptHash(), generalStore.ScriptHash(), 50_00000000, nil)
		customerInvoker.Invoke(t, true, "spend", customer.ScriptHash(), generalStore.ScriptHash(), 50_00000000, nil)
		// Total balance should be reduced by 150
		c.Invoke(t, 50_00000000, "balanceOf", customer.ScriptHash())
	})

	t.Run("vendor has correct total", func(t *testing.T) {
		c.Invoke(t, 150_00000000, "balanceOf", generalStore.ScriptHash())
	})
}

// TestVTS_AgeRestrictedVendor tests age verification for age-restricted vendors.
func TestVTS_AgeRestrictedVendor(t *testing.T) {
	c := newVTSClient(t)
	e := c.Executor

	adultCustomer := e.NewAccount(t)
	minorCustomer := e.NewAccount(t)
	liquorStore := e.NewAccount(t)
	committeeInvoker := c.WithSigners(c.Committee)
	adultInvoker := c.WithSigners(adultCustomer)
	minorInvoker := c.WithSigners(minorCustomer)

	// Register Vita for both customers
	registerVitaForVTS(t, e, adultCustomer)
	registerVitaForVTS(t, e, minorCustomer)

	// Only adult has age_verified attribute
	addAgeVerifiedAttribute(t, e, c.Committee, adultCustomer)

	// Register age-restricted vendor (liquor store)
	committeeInvoker.Invoke(t, true, "registerVendor", liquorStore.ScriptHash(), "Liquor Store", state.CategoryFood, true)

	// Mint VTS to both customers
	committeeInvoker.Invoke(t, true, "mint", adultCustomer.ScriptHash(), 100_00000000)
	committeeInvoker.Invoke(t, true, "mint", minorCustomer.ScriptHash(), 100_00000000)

	t.Run("minor cannot spend at age-restricted vendor", func(t *testing.T) {
		minorInvoker.InvokeFail(t, "age verification required for this vendor", "spend", minorCustomer.ScriptHash(), liquorStore.ScriptHash(), 10_00000000, nil)
	})

	t.Run("adult can spend at age-restricted vendor", func(t *testing.T) {
		adultInvoker.Invoke(t, true, "spend", adultCustomer.ScriptHash(), liquorStore.ScriptHash(), 10_00000000, nil)
	})

	t.Run("canSpendAt returns false for minor at age-restricted vendor", func(t *testing.T) {
		c.Invoke(t, false, "canSpendAt", minorCustomer.ScriptHash(), liquorStore.ScriptHash(), 10_00000000)
	})

	t.Run("canSpendAt returns true for adult at age-restricted vendor", func(t *testing.T) {
		c.Invoke(t, true, "canSpendAt", adultCustomer.ScriptHash(), liquorStore.ScriptHash(), 10_00000000)
	})
}

// TestVTS_MintRestrictedRequiresVita tests that minting restricted VTS requires a Vita.
func TestVTS_MintRestrictedRequiresVita(t *testing.T) {
	c := newVTSClient(t)
	e := c.Executor

	accWithVita := e.NewAccount(t)
	accWithoutVita := e.NewAccount(t)
	committeeInvoker := c.WithSigners(c.Committee)

	// Only register Vita for one account
	registerVitaForVTS(t, e, accWithVita)

	t.Run("cannot mint restricted VTS to account without Vita", func(t *testing.T) {
		committeeInvoker.InvokeFail(t, "restricted VTS requires Vita", "mintRestricted", accWithoutVita.ScriptHash(), 100_00000000, state.CategoryFood)
	})

	t.Run("can mint restricted VTS to account with Vita", func(t *testing.T) {
		committeeInvoker.Invoke(t, true, "mintRestricted", accWithVita.ScriptHash(), 100_00000000, state.CategoryFood)
	})
}
