package state

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

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

// Spending category constants (bitmask).
const (
	CategoryUnrestricted uint8 = 0        // No restrictions (standard money)
	CategoryFood         uint8 = 1 << 0   // Bit 0: Food purchases
	CategoryShelter      uint8 = 1 << 1   // Bit 1: Rent/utilities/housing
	CategoryMedical      uint8 = 1 << 2   // Bit 2: Healthcare expenses
	CategoryEducation    uint8 = 1 << 3   // Bit 3: Tuition/books/supplies
	CategoryTransport    uint8 = 1 << 4   // Bit 4: Transportation
	CategoryAll          uint8 = 0xFF     // All categories
)

// TxType represents the type of VTS transaction.
type TxType uint8

const (
	TxTypeTransfer TxType = 0 // P2P transfer (neutral)
	TxTypeIncome   TxType = 1 // Wages, sales, dividends (taxable)
	TxTypeExpense  TxType = 2 // Purchases, bills (potentially deductible)
	TxTypeBenefit  TxType = 3 // Government benefits (tax-exempt income)
	TxTypeGift     TxType = 4 // Gift (may have gift tax implications)
	TxTypeTax      TxType = 5 // Tax payment/withholding
)

// VTSBalance represents the balance state of a VTS token holder.
// It tracks both unrestricted and category-restricted balances.
type VTSBalance struct {
	Unrestricted big.Int            // Unrestricted balance (can be spent anywhere)
	Restricted   map[uint8]*big.Int // Category -> restricted balance
}

// NewVTSBalance creates a new empty VTSBalance.
func NewVTSBalance() *VTSBalance {
	return &VTSBalance{
		Restricted: make(map[uint8]*big.Int),
	}
}

// Total returns the total balance (unrestricted + all restricted).
func (b *VTSBalance) Total() *big.Int {
	total := new(big.Int).Set(&b.Unrestricted)
	for _, amt := range b.Restricted {
		total.Add(total, amt)
	}
	return total
}

// ToStackItem converts VTSBalance to stackitem.
func (b *VTSBalance) ToStackItem() (stackitem.Item, error) {
	// Format: [unrestricted, [[category, amount], [category, amount], ...]]
	restrictedItems := make([]stackitem.Item, 0, len(b.Restricted))
	for cat, amt := range b.Restricted {
		if amt.Sign() > 0 {
			restrictedItems = append(restrictedItems, stackitem.NewArray([]stackitem.Item{
				stackitem.NewBigInteger(big.NewInt(int64(cat))),
				stackitem.NewBigInteger(amt),
			}))
		}
	}
	return stackitem.NewStruct([]stackitem.Item{
		stackitem.NewBigInteger(&b.Unrestricted),
		stackitem.NewArray(restrictedItems),
	}), nil
}

// FromStackItem converts stackitem to VTSBalance.
func (b *VTSBalance) FromStackItem(item stackitem.Item) error {
	structItems, ok := item.Value().([]stackitem.Item)
	if !ok || len(structItems) < 2 {
		return errors.New("invalid VTSBalance structure")
	}

	unrestricted, err := structItems[0].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid unrestricted balance: %w", err)
	}
	b.Unrestricted = *unrestricted

	restrictedArr, ok := structItems[1].Value().([]stackitem.Item)
	if !ok {
		return errors.New("invalid restricted balances array")
	}

	b.Restricted = make(map[uint8]*big.Int)
	for _, ri := range restrictedArr {
		pair, ok := ri.Value().([]stackitem.Item)
		if !ok || len(pair) != 2 {
			return errors.New("invalid restricted balance pair")
		}
		catBI, err := pair[0].TryInteger()
		if err != nil {
			return fmt.Errorf("invalid category: %w", err)
		}
		amt, err := pair[1].TryInteger()
		if err != nil {
			return fmt.Errorf("invalid amount: %w", err)
		}
		b.Restricted[uint8(catBI.Int64())] = amt
	}
	return nil
}

// Vendor represents a registered vendor/merchant that can accept VTS payments.
type Vendor struct {
	Address       util.Uint160 // Vendor's script hash
	Name          string       // Display name (max 64 chars)
	Categories    uint8        // Bitmask of accepted spending categories
	RegisteredAt  uint32       // Block height when registered
	RegisteredBy  util.Uint160 // Who registered this vendor
	Active        bool         // Whether vendor is currently active
	AgeRestricted bool         // Whether vendor requires age verification (e.g., alcohol, tobacco)
}

// ToStackItem converts Vendor to stackitem.
func (v *Vendor) ToStackItem() (stackitem.Item, error) {
	return stackitem.NewStruct([]stackitem.Item{
		stackitem.NewByteArray(v.Address.BytesBE()),
		stackitem.NewByteArray([]byte(v.Name)),
		stackitem.NewBigInteger(big.NewInt(int64(v.Categories))),
		stackitem.NewBigInteger(big.NewInt(int64(v.RegisteredAt))),
		stackitem.NewByteArray(v.RegisteredBy.BytesBE()),
		stackitem.NewBool(v.Active),
		stackitem.NewBool(v.AgeRestricted),
	}), nil
}

// FromStackItem converts stackitem to Vendor.
func (v *Vendor) FromStackItem(item stackitem.Item) error {
	structItems, ok := item.Value().([]stackitem.Item)
	if !ok || len(structItems) < 6 {
		return errors.New("invalid Vendor structure")
	}

	addrBytes, err := structItems[0].TryBytes()
	if err != nil {
		return fmt.Errorf("invalid address: %w", err)
	}
	v.Address, err = util.Uint160DecodeBytesBE(addrBytes)
	if err != nil {
		return fmt.Errorf("invalid address bytes: %w", err)
	}

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

	catBI, err := structItems[2].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid categories: %w", err)
	}
	v.Categories = uint8(catBI.Int64())

	regAtBI, err := structItems[3].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid registeredAt: %w", err)
	}
	v.RegisteredAt = uint32(regAtBI.Int64())

	regByBytes, err := structItems[4].TryBytes()
	if err != nil {
		return fmt.Errorf("invalid registeredBy: %w", err)
	}
	v.RegisteredBy, err = util.Uint160DecodeBytesBE(regByBytes)
	if err != nil {
		return fmt.Errorf("invalid registeredBy bytes: %w", err)
	}

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

	// AgeRestricted is optional for backwards compatibility
	if len(structItems) >= 7 {
		ageRestricted, err := structItems[6].TryBool()
		if err != nil {
			return fmt.Errorf("invalid ageRestricted: %w", err)
		}
		v.AgeRestricted = ageRestricted
	}

	return nil
}

// TransactionRecord represents a recorded VTS transaction for tax accounting.
type TransactionRecord struct {
	TxHash      util.Uint256 // Transaction hash
	BlockHeight uint32       // When it occurred
	From        util.Uint160 // Sender
	To          util.Uint160 // Recipient
	Amount      big.Int      // Gross amount
	TxType      TxType       // Type of transaction
	Category    uint8        // Spending category (if applicable)
	TaxWithheld big.Int      // Tax withheld at source
	TaxRate     uint16       // Rate applied (basis points, e.g., 2500 = 25%)
	Memo        string       // Optional description (max 256 chars)
}

// ToStackItem converts TransactionRecord to stackitem.
func (t *TransactionRecord) ToStackItem() (stackitem.Item, error) {
	return stackitem.NewStruct([]stackitem.Item{
		stackitem.NewByteArray(t.TxHash.BytesBE()),
		stackitem.NewBigInteger(big.NewInt(int64(t.BlockHeight))),
		stackitem.NewByteArray(t.From.BytesBE()),
		stackitem.NewByteArray(t.To.BytesBE()),
		stackitem.NewBigInteger(&t.Amount),
		stackitem.NewBigInteger(big.NewInt(int64(t.TxType))),
		stackitem.NewBigInteger(big.NewInt(int64(t.Category))),
		stackitem.NewBigInteger(&t.TaxWithheld),
		stackitem.NewBigInteger(big.NewInt(int64(t.TaxRate))),
		stackitem.NewByteArray([]byte(t.Memo)),
	}), nil
}

// FromStackItem converts stackitem to TransactionRecord.
func (t *TransactionRecord) FromStackItem(item stackitem.Item) error {
	structItems, ok := item.Value().([]stackitem.Item)
	if !ok || len(structItems) < 10 {
		return errors.New("invalid TransactionRecord structure")
	}

	txHashBytes, err := structItems[0].TryBytes()
	if err != nil {
		return fmt.Errorf("invalid txHash: %w", err)
	}
	t.TxHash, err = util.Uint256DecodeBytesBE(txHashBytes)
	if err != nil {
		return fmt.Errorf("invalid txHash bytes: %w", err)
	}

	blockHeightBI, err := structItems[1].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid blockHeight: %w", err)
	}
	t.BlockHeight = uint32(blockHeightBI.Int64())

	fromBytes, err := structItems[2].TryBytes()
	if err != nil {
		return fmt.Errorf("invalid from: %w", err)
	}
	t.From, err = util.Uint160DecodeBytesBE(fromBytes)
	if err != nil {
		return fmt.Errorf("invalid from bytes: %w", err)
	}

	toBytes, err := structItems[3].TryBytes()
	if err != nil {
		return fmt.Errorf("invalid to: %w", err)
	}
	t.To, err = util.Uint160DecodeBytesBE(toBytes)
	if err != nil {
		return fmt.Errorf("invalid to bytes: %w", err)
	}

	amount, err := structItems[4].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid amount: %w", err)
	}
	t.Amount = *amount

	txTypeBI, err := structItems[5].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid txType: %w", err)
	}
	t.TxType = TxType(txTypeBI.Int64())

	catBI, err := structItems[6].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid category: %w", err)
	}
	t.Category = uint8(catBI.Int64())

	taxWithheld, err := structItems[7].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid taxWithheld: %w", err)
	}
	t.TaxWithheld = *taxWithheld

	taxRateBI, err := structItems[8].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid taxRate: %w", err)
	}
	t.TaxRate = uint16(taxRateBI.Int64())

	memoBytes, err := structItems[9].TryBytes()
	if err != nil {
		return fmt.Errorf("invalid memo: %w", err)
	}
	t.Memo = string(memoBytes)

	return nil
}

// TaxConfig represents the tax configuration for VTS.
type TaxConfig struct {
	DefaultIncomeRate uint16       // Default income tax rate (basis points)
	DefaultSalesRate  uint16       // Default sales tax rate (basis points)
	TreasuryAddress   util.Uint160 // Where taxes are sent
	ExemptCategories  uint8        // Categories exempt from sales tax (bitmask)
}

// ToStackItem converts TaxConfig to stackitem.
func (c *TaxConfig) ToStackItem() (stackitem.Item, error) {
	return stackitem.NewStruct([]stackitem.Item{
		stackitem.NewBigInteger(big.NewInt(int64(c.DefaultIncomeRate))),
		stackitem.NewBigInteger(big.NewInt(int64(c.DefaultSalesRate))),
		stackitem.NewByteArray(c.TreasuryAddress.BytesBE()),
		stackitem.NewBigInteger(big.NewInt(int64(c.ExemptCategories))),
	}), nil
}

// FromStackItem converts stackitem to TaxConfig.
func (c *TaxConfig) FromStackItem(item stackitem.Item) error {
	structItems, ok := item.Value().([]stackitem.Item)
	if !ok || len(structItems) < 4 {
		return errors.New("invalid TaxConfig structure")
	}

	incomeRateBI, err := structItems[0].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid defaultIncomeRate: %w", err)
	}
	c.DefaultIncomeRate = uint16(incomeRateBI.Int64())

	salesRateBI, err := structItems[1].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid defaultSalesRate: %w", err)
	}
	c.DefaultSalesRate = uint16(salesRateBI.Int64())

	treasuryBytes, err := structItems[2].TryBytes()
	if err != nil {
		return fmt.Errorf("invalid treasuryAddress: %w", err)
	}
	c.TreasuryAddress, err = util.Uint160DecodeBytesBE(treasuryBytes)
	if err != nil {
		return fmt.Errorf("invalid treasuryAddress bytes: %w", err)
	}

	exemptCatBI, err := structItems[3].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid exemptCategories: %w", err)
	}
	c.ExemptCategories = uint8(exemptCatBI.Int64())

	return nil
}

// TaxSummary represents a tax summary for a specific period.
type TaxSummary struct {
	Account            util.Uint160 // Account address
	StartBlock         uint32       // Period start
	EndBlock           uint32       // Period end
	TotalIncome        big.Int      // Total taxable income
	TotalBenefits      big.Int      // Total tax-exempt benefits
	TotalExpenses      big.Int      // Total expenses (for deductions)
	DeductibleExpenses big.Int      // Expenses that are deductible
	TaxWithheld        big.Int      // Total tax withheld
	EstimatedOwed      big.Int      // Estimated tax owed (income * rate)
	Balance            big.Int      // TaxWithheld - EstimatedOwed
}

// ToStackItem converts TaxSummary to stackitem.
func (s *TaxSummary) ToStackItem() (stackitem.Item, error) {
	return stackitem.NewStruct([]stackitem.Item{
		stackitem.NewByteArray(s.Account.BytesBE()),
		stackitem.NewBigInteger(big.NewInt(int64(s.StartBlock))),
		stackitem.NewBigInteger(big.NewInt(int64(s.EndBlock))),
		stackitem.NewBigInteger(&s.TotalIncome),
		stackitem.NewBigInteger(&s.TotalBenefits),
		stackitem.NewBigInteger(&s.TotalExpenses),
		stackitem.NewBigInteger(&s.DeductibleExpenses),
		stackitem.NewBigInteger(&s.TaxWithheld),
		stackitem.NewBigInteger(&s.EstimatedOwed),
		stackitem.NewBigInteger(&s.Balance),
	}), nil
}

// FromStackItem converts stackitem to TaxSummary.
func (s *TaxSummary) FromStackItem(item stackitem.Item) error {
	structItems, ok := item.Value().([]stackitem.Item)
	if !ok || len(structItems) < 10 {
		return errors.New("invalid TaxSummary structure")
	}

	accountBytes, err := structItems[0].TryBytes()
	if err != nil {
		return fmt.Errorf("invalid account: %w", err)
	}
	s.Account, err = util.Uint160DecodeBytesBE(accountBytes)
	if err != nil {
		return fmt.Errorf("invalid account bytes: %w", err)
	}

	startBI, err := structItems[1].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid startBlock: %w", err)
	}
	s.StartBlock = uint32(startBI.Int64())

	endBI, err := structItems[2].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid endBlock: %w", err)
	}
	s.EndBlock = uint32(endBI.Int64())

	totalIncome, err := structItems[3].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid totalIncome: %w", err)
	}
	s.TotalIncome = *totalIncome

	totalBenefits, err := structItems[4].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid totalBenefits: %w", err)
	}
	s.TotalBenefits = *totalBenefits

	totalExpenses, err := structItems[5].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid totalExpenses: %w", err)
	}
	s.TotalExpenses = *totalExpenses

	deductible, err := structItems[6].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid deductibleExpenses: %w", err)
	}
	s.DeductibleExpenses = *deductible

	taxWithheld, err := structItems[7].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid taxWithheld: %w", err)
	}
	s.TaxWithheld = *taxWithheld

	estimatedOwed, err := structItems[8].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid estimatedOwed: %w", err)
	}
	s.EstimatedOwed = *estimatedOwed

	balance, err := structItems[9].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid balance: %w", err)
	}
	s.Balance = *balance

	return nil
}
