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

// Storage key prefixes for Eligere.
const (
	eligerePrefixProposal        byte = 0x01 // proposalID -> Proposal
	eligerePrefixProposalTitle   byte = 0x02 // titleHash -> proposalID
	eligerePrefixVote            byte = 0x03 // proposalID + vitaID -> Vote
	eligerePrefixVoterHistory    byte = 0x04 // vitaID + proposalID -> exists
	eligerePrefixProposalStatus  byte = 0x05 // status + proposalID -> exists
	eligerePrefixCategory        byte = 0x06 // category + proposalID -> exists
	eligerePrefixActiveProposals byte = 0x07 // -> serialized []proposalID
	eligerePrefixProposalCounter byte = 0xF0 // -> next proposalID
	eligerePrefixConfig          byte = 0xF1 // -> EligereConfig
)

// Event names for Eligere.
const (
	ProposalCreatedEvent   = "ProposalCreated"
	ProposalActivatedEvent = "ProposalActivated"
	VoteCastEvent          = "VoteCast"
	ProposalPassedEvent    = "ProposalPassed"
	ProposalRejectedEvent  = "ProposalRejected"
	ProposalExecutedEvent  = "ProposalExecuted"
	ProposalCancelledEvent = "ProposalCancelled"
	ProposalExpiredEvent   = "ProposalExpired"
)

// Errors for Eligere.
var (
	ErrProposalNotFound        = errors.New("proposal not found")
	ErrProposalNotActive       = errors.New("proposal is not active for voting")
	ErrAlreadyVoted            = errors.New("already voted on this proposal")
	ErrVotingNotStarted        = errors.New("voting period has not started")
	ErrVotingEnded             = errors.New("voting period has ended")
	ErrNotProposer             = errors.New("caller is not the proposer")
	ErrProposalTitleExists     = errors.New("proposal title already exists")
	ErrInvalidCategory         = errors.New("invalid proposal category")
	ErrQuorumNotMet            = errors.New("quorum not met")
	ErrThresholdNotMet         = errors.New("threshold not met")
	ErrProposalNotPassed       = errors.New("proposal has not passed")
	ErrExecutionDelayNotPassed = errors.New("execution delay has not passed")
	ErrProposalAlreadyExecuted = errors.New("proposal already executed")
	ErrNoVitaToken             = errors.New("caller must have active Vita")
	ErrVotingRightRestricted   = errors.New("voting right is restricted")
	ErrUnderVotingAge          = errors.New("voter is under voting age")
	ErrInvalidVotingPeriod     = errors.New("invalid voting period")
	ErrTitleTooLong            = errors.New("title too long (max 128 chars)")
	ErrInvalidVoteChoice       = errors.New("invalid vote choice")
	ErrVotingPeriodNotEnded    = errors.New("voting period has not ended")
	ErrProposalAlreadyFinalized = errors.New("proposal already finalized")
	ErrNotCommitteeOrProposer  = errors.New("only proposer or committee can cancel")
	ErrCannotCancelFinalized   = errors.New("cannot cancel finalized proposal")
)

// Eligere represents the democratic voting native contract.
type Eligere struct {
	interop.ContractMD

	Tutus        ITutus
	Vita         IVita
	RoleRegistry IRoleRegistry
	Lex          ILex
	Annos        IAnnos
}

// EligereCache contains cached data for performance.
type EligereCache struct {
	proposalCount uint64
}

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

// newEligere creates a new Eligere native contract.
func newEligere() *Eligere {
	e := &Eligere{}

	e.ContractMD = *interop.NewContractMD(nativenames.Eligere, nativeids.Eligere)
	defer e.BuildHFSpecificMD(e.ActiveIn())

	// ============ Proposal Management Methods ============

	desc := NewDescriptor("createProposal", smartcontract.IntegerType,
		manifest.NewParameter("title", smartcontract.StringType),
		manifest.NewParameter("contentHash", smartcontract.ByteArrayType),
		manifest.NewParameter("category", smartcontract.IntegerType),
		manifest.NewParameter("votingStartsAt", smartcontract.IntegerType),
		manifest.NewParameter("votingEndsAt", smartcontract.IntegerType),
		manifest.NewParameter("targetContract", smartcontract.Hash160Type),
		manifest.NewParameter("targetMethod", smartcontract.StringType),
		manifest.NewParameter("targetParams", smartcontract.ByteArrayType))
	md := NewMethodAndPrice(e.createProposal, 1<<15, callflag.States|callflag.AllowNotify)
	e.AddMethod(md, desc)

	desc = NewDescriptor("cancelProposal", smartcontract.BoolType,
		manifest.NewParameter("proposalID", smartcontract.IntegerType))
	md = NewMethodAndPrice(e.cancelProposal, 1<<15, callflag.States|callflag.AllowNotify)
	e.AddMethod(md, desc)

	// ============ Voting Methods ============

	desc = NewDescriptor("vote", smartcontract.BoolType,
		manifest.NewParameter("proposalID", smartcontract.IntegerType),
		manifest.NewParameter("choice", smartcontract.IntegerType))
	md = NewMethodAndPrice(e.vote, 1<<15, callflag.States|callflag.AllowNotify)
	e.AddMethod(md, desc)

	// ============ Tallying & Execution Methods ============

	desc = NewDescriptor("tallyVotes", smartcontract.BoolType,
		manifest.NewParameter("proposalID", smartcontract.IntegerType))
	md = NewMethodAndPrice(e.tallyVotes, 1<<15, callflag.States|callflag.AllowNotify)
	e.AddMethod(md, desc)

	desc = NewDescriptor("executeProposal", smartcontract.BoolType,
		manifest.NewParameter("proposalID", smartcontract.IntegerType))
	md = NewMethodAndPrice(e.executeProposal, 1<<17, callflag.All)
	e.AddMethod(md, desc)

	// ============ Query Methods ============

	desc = NewDescriptor("getProposal", smartcontract.ArrayType,
		manifest.NewParameter("proposalID", smartcontract.IntegerType))
	md = NewMethodAndPrice(e.getProposal, 1<<15, callflag.ReadStates)
	e.AddMethod(md, desc)

	desc = NewDescriptor("hasVoted", smartcontract.BoolType,
		manifest.NewParameter("proposalID", smartcontract.IntegerType),
		manifest.NewParameter("voter", smartcontract.Hash160Type))
	md = NewMethodAndPrice(e.hasVoted, 1<<15, callflag.ReadStates)
	e.AddMethod(md, desc)

	desc = NewDescriptor("getVote", smartcontract.ArrayType,
		manifest.NewParameter("proposalID", smartcontract.IntegerType),
		manifest.NewParameter("voter", smartcontract.Hash160Type))
	md = NewMethodAndPrice(e.getVote, 1<<15, callflag.ReadStates)
	e.AddMethod(md, desc)

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

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

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

	eDesc := NewEventDescriptor(ProposalCreatedEvent,
		manifest.NewParameter("proposalID", smartcontract.IntegerType),
		manifest.NewParameter("title", smartcontract.StringType),
		manifest.NewParameter("category", smartcontract.IntegerType),
		manifest.NewParameter("proposer", smartcontract.Hash160Type),
		manifest.NewParameter("votingEndsAt", smartcontract.IntegerType))
	e.AddEvent(NewEvent(eDesc))

	eDesc = NewEventDescriptor(ProposalActivatedEvent,
		manifest.NewParameter("proposalID", smartcontract.IntegerType))
	e.AddEvent(NewEvent(eDesc))

	eDesc = NewEventDescriptor(VoteCastEvent,
		manifest.NewParameter("proposalID", smartcontract.IntegerType),
		manifest.NewParameter("voterVitaID", smartcontract.IntegerType),
		manifest.NewParameter("choice", smartcontract.IntegerType))
	e.AddEvent(NewEvent(eDesc))

	eDesc = NewEventDescriptor(ProposalPassedEvent,
		manifest.NewParameter("proposalID", smartcontract.IntegerType),
		manifest.NewParameter("supportPercent", smartcontract.IntegerType),
		manifest.NewParameter("totalVotes", smartcontract.IntegerType))
	e.AddEvent(NewEvent(eDesc))

	eDesc = NewEventDescriptor(ProposalRejectedEvent,
		manifest.NewParameter("proposalID", smartcontract.IntegerType),
		manifest.NewParameter("supportPercent", smartcontract.IntegerType))
	e.AddEvent(NewEvent(eDesc))

	eDesc = NewEventDescriptor(ProposalExecutedEvent,
		manifest.NewParameter("proposalID", smartcontract.IntegerType),
		manifest.NewParameter("executor", smartcontract.Hash160Type),
		manifest.NewParameter("executedAt", smartcontract.IntegerType))
	e.AddEvent(NewEvent(eDesc))

	eDesc = NewEventDescriptor(ProposalCancelledEvent,
		manifest.NewParameter("proposalID", smartcontract.IntegerType),
		manifest.NewParameter("cancelledBy", smartcontract.Hash160Type))
	e.AddEvent(NewEvent(eDesc))

	eDesc = NewEventDescriptor(ProposalExpiredEvent,
		manifest.NewParameter("proposalID", smartcontract.IntegerType),
		manifest.NewParameter("reason", smartcontract.StringType))
	e.AddEvent(NewEvent(eDesc))

	return e
}

// Initialize implements the Contract interface.
func (e *Eligere) Initialize(ic *interop.Context, hf *config.Hardfork, newMD *interop.HFSpecificContractMD) error {
	if hf != e.ActiveIn() {
		return nil
	}

	// Initialize cache
	cache := &EligereCache{proposalCount: 0}
	ic.DAO.SetCache(e.ID, cache)

	// Initialize config with defaults
	cfg := state.DefaultEligereConfig()
	if err := e.putConfig(ic.DAO, cfg); err != nil {
		return err
	}

	// Initialize proposal counter
	setIntWithKey(e.ID, ic.DAO, []byte{eligerePrefixProposalCounter}, 0)

	return nil
}

// InitializeCache implements the Contract interface.
func (e *Eligere) InitializeCache(_ interop.IsHardforkEnabled, blockHeight uint32, d *dao.Simple) error {
	cache := &EligereCache{
		proposalCount: e.getProposalCounter(d),
	}
	d.SetCache(e.ID, cache)
	return nil
}

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

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

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

// Address returns the contract's script hash.
func (e *Eligere) Address() util.Uint160 {
	return e.Hash
}

// GetProposalInternal returns a proposal by ID (for cross-native access).
func (e *Eligere) GetProposalInternal(d *dao.Simple, proposalID uint64) *state.Proposal {
	return e.getProposalInternal(d, proposalID)
}

// ActiveIn implements the Contract interface.
func (e *Eligere) ActiveIn() *config.Hardfork {
	return nil // Active from genesis
}

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

func (e *Eligere) makeProposalKey(proposalID uint64) []byte {
	key := make([]byte, 1+8)
	key[0] = eligerePrefixProposal
	binary.BigEndian.PutUint64(key[1:], proposalID)
	return key
}

func (e *Eligere) makeTitleKey(title string) []byte {
	h := hash.Hash160([]byte(title))
	key := make([]byte, 1+20)
	key[0] = eligerePrefixProposalTitle
	copy(key[1:], h.BytesBE())
	return key
}

func (e *Eligere) makeVoteKey(proposalID, vitaID uint64) []byte {
	key := make([]byte, 1+8+8)
	key[0] = eligerePrefixVote
	binary.BigEndian.PutUint64(key[1:9], proposalID)
	binary.BigEndian.PutUint64(key[9:], vitaID)
	return key
}

func (e *Eligere) makeVoterHistoryKey(vitaID, proposalID uint64) []byte {
	key := make([]byte, 1+8+8)
	key[0] = eligerePrefixVoterHistory
	binary.BigEndian.PutUint64(key[1:9], vitaID)
	binary.BigEndian.PutUint64(key[9:], proposalID)
	return key
}

func (e *Eligere) makeStatusIndexKey(status state.ProposalStatus, proposalID uint64) []byte {
	key := make([]byte, 1+1+8)
	key[0] = eligerePrefixProposalStatus
	key[1] = byte(status)
	binary.BigEndian.PutUint64(key[2:], proposalID)
	return key
}

func (e *Eligere) makeCategoryIndexKey(category state.ProposalCategory, proposalID uint64) []byte {
	key := make([]byte, 1+1+8)
	key[0] = eligerePrefixCategory
	key[1] = byte(category)
	binary.BigEndian.PutUint64(key[2:], proposalID)
	return key
}

// ============ Proposal Storage ============

func (e *Eligere) getProposalInternal(d *dao.Simple, proposalID uint64) *state.Proposal {
	key := e.makeProposalKey(proposalID)
	proposal := &state.Proposal{}
	err := getConvertibleFromDAO(e.ID, d, key, proposal)
	if err != nil {
		return nil
	}
	return proposal
}

func (e *Eligere) putProposal(d *dao.Simple, proposal *state.Proposal) error {
	key := e.makeProposalKey(proposal.ID)
	return putConvertibleToDAO(e.ID, d, key, proposal)
}

func (e *Eligere) titleExists(d *dao.Simple, title string) bool {
	key := e.makeTitleKey(title)
	si := d.GetStorageItem(e.ID, key)
	return si != nil
}

func (e *Eligere) putTitleIndex(d *dao.Simple, title string, proposalID uint64) {
	key := e.makeTitleKey(title)
	val := make([]byte, 8)
	binary.BigEndian.PutUint64(val, proposalID)
	d.PutStorageItem(e.ID, key, val)
}

// ============ Vote Storage ============

func (e *Eligere) getVoteInternal(d *dao.Simple, proposalID, vitaID uint64) *state.Vote {
	key := e.makeVoteKey(proposalID, vitaID)
	vote := &state.Vote{}
	err := getConvertibleFromDAO(e.ID, d, key, vote)
	if err != nil {
		return nil
	}
	return vote
}

func (e *Eligere) putVote(d *dao.Simple, vote *state.Vote) error {
	key := e.makeVoteKey(vote.ProposalID, vote.VoterVitaID)
	return putConvertibleToDAO(e.ID, d, key, vote)
}

func (e *Eligere) hasVotedInternal(d *dao.Simple, proposalID, vitaID uint64) bool {
	key := e.makeVoteKey(proposalID, vitaID)
	si := d.GetStorageItem(e.ID, key)
	return si != nil
}

func (e *Eligere) putVoterHistory(d *dao.Simple, vitaID, proposalID uint64) {
	key := e.makeVoterHistoryKey(vitaID, proposalID)
	d.PutStorageItem(e.ID, key, []byte{1})
}

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

func (e *Eligere) getConfigInternal(d *dao.Simple) *state.EligereConfig {
	key := []byte{eligerePrefixConfig}
	cfg := &state.EligereConfig{}
	err := getConvertibleFromDAO(e.ID, d, key, cfg)
	if err != nil {
		return state.DefaultEligereConfig()
	}
	return cfg
}

func (e *Eligere) putConfig(d *dao.Simple, cfg *state.EligereConfig) error {
	key := []byte{eligerePrefixConfig}
	return putConvertibleToDAO(e.ID, d, key, cfg)
}

// ============ Counter ============

func (e *Eligere) getProposalCounter(d *dao.Simple) uint64 {
	key := []byte{eligerePrefixProposalCounter}
	return uint64(getIntWithKey(e.ID, d, key))
}

func (e *Eligere) getAndIncrementProposalCounter(d *dao.Simple) uint64 {
	key := []byte{eligerePrefixProposalCounter}
	current := getIntWithKey(e.ID, d, key)
	setIntWithKey(e.ID, d, key, current+1)
	return uint64(current + 1)
}

// ============ Status Index ============

func (e *Eligere) updateStatusIndex(d *dao.Simple, proposalID uint64, oldStatus, newStatus state.ProposalStatus) {
	// Remove from old status index
	if oldStatus != newStatus {
		oldKey := e.makeStatusIndexKey(oldStatus, proposalID)
		d.DeleteStorageItem(e.ID, oldKey)
	}
	// Add to new status index
	newKey := e.makeStatusIndexKey(newStatus, proposalID)
	d.PutStorageItem(e.ID, newKey, []byte{1})
}

// ============ Authorization Helpers ============

func (e *Eligere) checkCommittee(ic *interop.Context) bool {
	if e.Tutus == nil {
		return false
	}
	return e.Tutus.CheckCommittee(ic)
}

func (e *Eligere) hasLegislatorRole(d *dao.Simple, addr util.Uint160, blockHeight uint32) bool {
	if e.RoleRegistry == nil {
		return false
	}
	return e.RoleRegistry.HasRoleInternal(d, addr, RoleLegislator, blockHeight)
}

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

// createProposal creates a new democratic proposal.
func (e *Eligere) createProposal(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	title := toString(args[0])
	contentHashBytes := toBytes(args[1])
	category := state.ProposalCategory(toBigInt(args[2]).Uint64())
	votingStartsAt := uint32(toBigInt(args[3]).Uint64())
	votingEndsAt := uint32(toBigInt(args[4]).Uint64())
	targetContract := toUint160(args[5])
	targetMethod := toString(args[6])
	targetParams := toBytes(args[7])

	caller := ic.VM.GetCallingScriptHash()

	// Validate caller has active Vita
	token, err := e.Vita.GetTokenByOwner(ic.DAO, caller)
	if err != nil || token == nil || token.Status != state.TokenStatusActive {
		panic(ErrNoVitaToken)
	}

	// Check voting right is not restricted (via Lex)
	if e.Lex != nil && !e.Lex.HasRightInternal(ic.DAO, caller, state.RightVote, ic.Block.Index) {
		panic(ErrVotingRightRestricted)
	}

	// For law/constitutional amendments, require legislator role or committee
	if category == state.ProposalCategoryLawAmendment || category == state.ProposalCategoryConstitutional {
		if !e.hasLegislatorRole(ic.DAO, caller, ic.Block.Index) && !e.checkCommittee(ic) {
			panic("legislative authority required for law amendments")
		}
	}

	// Validate inputs
	if len(title) > 128 {
		panic(ErrTitleTooLong)
	}
	if len(title) == 0 {
		panic("title cannot be empty")
	}
	if category < state.ProposalCategoryLawAmendment || category > state.ProposalCategoryReferendum {
		panic(ErrInvalidCategory)
	}

	// Check title uniqueness
	if e.titleExists(ic.DAO, title) {
		panic(ErrProposalTitleExists)
	}

	// Validate voting period
	cfg := e.getConfigInternal(ic.DAO)
	if votingStartsAt < ic.Block.Index {
		votingStartsAt = ic.Block.Index // Can't start in past
	}
	duration := votingEndsAt - votingStartsAt
	if duration < cfg.MinVotingPeriod || duration > cfg.MaxVotingPeriod {
		panic(ErrInvalidVotingPeriod)
	}

	// Determine threshold based on category
	threshold := cfg.StandardThreshold
	if category == state.ProposalCategoryConstitutional {
		threshold = cfg.ConstitutionalThreshold
	}

	// Get next proposal ID
	proposalID := e.getAndIncrementProposalCounter(ic.DAO)

	// Create proposal
	var contentHash util.Uint256
	if len(contentHashBytes) == 32 {
		copy(contentHash[:], contentHashBytes)
	}

	proposal := &state.Proposal{
		ID:               proposalID,
		Title:            title,
		ContentHash:      contentHash,
		Category:         category,
		Proposer:         caller,
		ProposerVitaID:   token.TokenID,
		CreatedAt:        ic.Block.Index,
		VotingStartsAt:   votingStartsAt,
		VotingEndsAt:     votingEndsAt,
		ExecutionDelay:   cfg.DefaultExecutionDelay,
		QuorumPercent:    cfg.DefaultQuorum,
		ThresholdPercent: threshold,
		Status:           state.ProposalStatusDraft,
		TargetContract:   targetContract,
		TargetMethod:     targetMethod,
		TargetParams:     targetParams,
	}

	// If voting starts now, activate immediately
	if votingStartsAt <= ic.Block.Index {
		proposal.Status = state.ProposalStatusActive
	}

	// Store proposal
	if err := e.putProposal(ic.DAO, proposal); err != nil {
		panic(err)
	}

	// Store indexes
	e.putTitleIndex(ic.DAO, title, proposalID)
	e.updateStatusIndex(ic.DAO, proposalID, state.ProposalStatusDraft, proposal.Status)

	// Store category index
	catKey := e.makeCategoryIndexKey(category, proposalID)
	ic.DAO.PutStorageItem(e.ID, catKey, []byte{1})

	// Emit event
	ic.AddNotification(e.Hash, ProposalCreatedEvent, stackitem.NewArray([]stackitem.Item{
		stackitem.NewBigInteger(big.NewInt(int64(proposalID))),
		stackitem.NewByteArray([]byte(title)),
		stackitem.NewBigInteger(big.NewInt(int64(category))),
		stackitem.NewByteArray(caller.BytesBE()),
		stackitem.NewBigInteger(big.NewInt(int64(votingEndsAt))),
	}))

	return stackitem.NewBigInteger(big.NewInt(int64(proposalID)))
}

// cancelProposal cancels a proposal (proposer or committee only).
func (e *Eligere) cancelProposal(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	proposalID := toBigInt(args[0]).Uint64()

	caller := ic.VM.GetCallingScriptHash()

	proposal := e.getProposalInternal(ic.DAO, proposalID)
	if proposal == nil {
		panic(ErrProposalNotFound)
	}

	// Check authorization: must be proposer or committee
	isProposer := proposal.Proposer.Equals(caller)
	isCommittee := e.checkCommittee(ic)
	if !isProposer && !isCommittee {
		panic(ErrNotCommitteeOrProposer)
	}

	// Can only cancel draft or active proposals
	if proposal.Status != state.ProposalStatusDraft && proposal.Status != state.ProposalStatusActive {
		panic(ErrCannotCancelFinalized)
	}

	oldStatus := proposal.Status
	proposal.Status = state.ProposalStatusCancelled

	// Update proposal
	if err := e.putProposal(ic.DAO, proposal); err != nil {
		panic(err)
	}

	// Update status index
	e.updateStatusIndex(ic.DAO, proposalID, oldStatus, proposal.Status)

	// Emit event
	ic.AddNotification(e.Hash, ProposalCancelledEvent, stackitem.NewArray([]stackitem.Item{
		stackitem.NewBigInteger(big.NewInt(int64(proposalID))),
		stackitem.NewByteArray(caller.BytesBE()),
	}))

	return stackitem.NewBool(true)
}

// vote casts a vote on a proposal.
func (e *Eligere) vote(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	proposalID := toBigInt(args[0]).Uint64()
	choice := state.VoteChoice(toBigInt(args[1]).Uint64())

	caller := ic.VM.GetCallingScriptHash()

	// Validate vote choice
	if choice > state.VoteChoiceNo {
		panic(ErrInvalidVoteChoice)
	}

	// Validate caller has active Vita
	token, err := e.Vita.GetTokenByOwner(ic.DAO, caller)
	if err != nil || token == nil || token.Status != state.TokenStatusActive {
		panic(ErrNoVitaToken)
	}

	// Check voting right is not restricted
	if e.Lex != nil && !e.Lex.HasRightInternal(ic.DAO, caller, state.RightVote, ic.Block.Index) {
		panic(ErrVotingRightRestricted)
	}

	// Check voter is of voting age (18+)
	if e.Annos != nil && !e.Annos.IsVotingAgeInternal(ic.DAO, caller, ic.Block.Timestamp) {
		panic(ErrUnderVotingAge)
	}

	// Get proposal
	proposal := e.getProposalInternal(ic.DAO, proposalID)
	if proposal == nil {
		panic(ErrProposalNotFound)
	}

	// Check proposal is active
	if proposal.Status != state.ProposalStatusActive {
		// Auto-activate if draft and voting period has started
		if proposal.Status == state.ProposalStatusDraft && ic.Block.Index >= proposal.VotingStartsAt {
			proposal.Status = state.ProposalStatusActive
			e.updateStatusIndex(ic.DAO, proposalID, state.ProposalStatusDraft, state.ProposalStatusActive)
		} else {
			panic(ErrProposalNotActive)
		}
	}

	// Check voting period
	if ic.Block.Index < proposal.VotingStartsAt {
		panic(ErrVotingNotStarted)
	}
	if ic.Block.Index > proposal.VotingEndsAt {
		panic(ErrVotingEnded)
	}

	// Check not already voted (using Vita ID for one-person-one-vote)
	if e.hasVotedInternal(ic.DAO, proposalID, token.TokenID) {
		panic(ErrAlreadyVoted)
	}

	// Create vote record
	voteRecord := &state.Vote{
		ProposalID:  proposalID,
		VoterVitaID: token.TokenID,
		Voter:       caller,
		Choice:      choice,
		VotedAt:     ic.Block.Index,
		Weight:      1, // Equal voting weight
	}

	// Store vote
	if err := e.putVote(ic.DAO, voteRecord); err != nil {
		panic(err)
	}

	// Store voter history
	e.putVoterHistory(ic.DAO, token.TokenID, proposalID)

	// Update vote counts (incremental tallying)
	proposal.TotalVotes++
	switch choice {
	case state.VoteChoiceYes:
		proposal.YesVotes++
	case state.VoteChoiceNo:
		proposal.NoVotes++
	case state.VoteChoiceAbstain:
		proposal.AbstainVotes++
	}

	// Update proposal
	if err := e.putProposal(ic.DAO, proposal); err != nil {
		panic(err)
	}

	// Emit event
	ic.AddNotification(e.Hash, VoteCastEvent, stackitem.NewArray([]stackitem.Item{
		stackitem.NewBigInteger(big.NewInt(int64(proposalID))),
		stackitem.NewBigInteger(big.NewInt(int64(token.TokenID))),
		stackitem.NewBigInteger(big.NewInt(int64(choice))),
	}))

	return stackitem.NewBool(true)
}

// tallyVotes finalizes voting after the deadline.
func (e *Eligere) tallyVotes(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	proposalID := toBigInt(args[0]).Uint64()

	proposal := e.getProposalInternal(ic.DAO, proposalID)
	if proposal == nil {
		panic(ErrProposalNotFound)
	}

	// Must be after voting period
	if ic.Block.Index <= proposal.VotingEndsAt {
		panic(ErrVotingPeriodNotEnded)
	}

	// Must still be active
	if proposal.Status != state.ProposalStatusActive {
		panic(ErrProposalAlreadyFinalized)
	}

	// Get total eligible voters for quorum calculation
	// Use Vita token count as the voter base
	totalVoters := e.Vita.GetTotalTokenCount(ic.DAO)
	if totalVoters == 0 {
		totalVoters = 1 // Avoid division by zero
	}

	oldStatus := proposal.Status

	// Check quorum (total votes including abstentions)
	quorumRequired := (totalVoters * uint64(proposal.QuorumPercent)) / 100
	if proposal.TotalVotes < quorumRequired {
		proposal.Status = state.ProposalStatusExpired

		if err := e.putProposal(ic.DAO, proposal); err != nil {
			panic(err)
		}
		e.updateStatusIndex(ic.DAO, proposalID, oldStatus, proposal.Status)

		ic.AddNotification(e.Hash, ProposalExpiredEvent, stackitem.NewArray([]stackitem.Item{
			stackitem.NewBigInteger(big.NewInt(int64(proposalID))),
			stackitem.NewByteArray([]byte("quorum not met")),
		}))
		return stackitem.NewBool(false)
	}

	// Check threshold (yes votes vs yes+no, abstentions don't count toward threshold)
	totalDecisiveVotes := proposal.YesVotes + proposal.NoVotes
	var supportPercent uint64 = 0
	if totalDecisiveVotes > 0 {
		supportPercent = (proposal.YesVotes * 100) / totalDecisiveVotes
	}

	if totalDecisiveVotes == 0 || supportPercent < uint64(proposal.ThresholdPercent) {
		proposal.Status = state.ProposalStatusRejected

		if err := e.putProposal(ic.DAO, proposal); err != nil {
			panic(err)
		}
		e.updateStatusIndex(ic.DAO, proposalID, oldStatus, proposal.Status)

		ic.AddNotification(e.Hash, ProposalRejectedEvent, stackitem.NewArray([]stackitem.Item{
			stackitem.NewBigInteger(big.NewInt(int64(proposalID))),
			stackitem.NewBigInteger(big.NewInt(int64(supportPercent))),
		}))
		return stackitem.NewBool(false)
	}

	// Proposal passed
	proposal.Status = state.ProposalStatusPassed

	if err := e.putProposal(ic.DAO, proposal); err != nil {
		panic(err)
	}
	e.updateStatusIndex(ic.DAO, proposalID, oldStatus, proposal.Status)

	ic.AddNotification(e.Hash, ProposalPassedEvent, stackitem.NewArray([]stackitem.Item{
		stackitem.NewBigInteger(big.NewInt(int64(proposalID))),
		stackitem.NewBigInteger(big.NewInt(int64(supportPercent))),
		stackitem.NewBigInteger(big.NewInt(int64(proposal.TotalVotes))),
	}))

	return stackitem.NewBool(true)
}

// executeProposal executes a passed proposal after the execution delay.
func (e *Eligere) executeProposal(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	proposalID := toBigInt(args[0]).Uint64()

	caller := ic.VM.GetCallingScriptHash()

	proposal := e.getProposalInternal(ic.DAO, proposalID)
	if proposal == nil {
		panic(ErrProposalNotFound)
	}

	// Must have passed
	if proposal.Status != state.ProposalStatusPassed {
		panic(ErrProposalNotPassed)
	}

	// Check execution delay has passed
	executionBlock := proposal.VotingEndsAt + proposal.ExecutionDelay
	if ic.Block.Index < executionBlock {
		panic(ErrExecutionDelayNotPassed)
	}

	// Already executed check
	if proposal.ExecutedAt > 0 {
		panic(ErrProposalAlreadyExecuted)
	}

	oldStatus := proposal.Status

	// Execute based on category
	switch proposal.Category {
	case state.ProposalCategoryLawAmendment:
		// Call Lex.ratifyAmendment if Lex is available
		if e.Lex != nil {
			e.executeLawAmendment(ic, proposal)
		}
	case state.ProposalCategoryConstitutional:
		// Constitutional amendments also go through Lex
		if e.Lex != nil {
			e.executeLawAmendment(ic, proposal)
		}
	case state.ProposalCategoryGovernanceAction:
		// Governance actions may call target contract
		// Implementation depends on specific action
	case state.ProposalCategoryInvestment:
		// Investment proposals - future integration
	case state.ProposalCategoryReferendum:
		// Referendums are advisory, no automatic execution
	}

	// Update proposal status
	proposal.Status = state.ProposalStatusExecuted
	proposal.ExecutedAt = ic.Block.Index
	proposal.ExecutedBy = caller

	if err := e.putProposal(ic.DAO, proposal); err != nil {
		panic(err)
	}
	e.updateStatusIndex(ic.DAO, proposalID, oldStatus, proposal.Status)

	// Emit event
	ic.AddNotification(e.Hash, ProposalExecutedEvent, stackitem.NewArray([]stackitem.Item{
		stackitem.NewBigInteger(big.NewInt(int64(proposalID))),
		stackitem.NewByteArray(caller.BytesBE()),
		stackitem.NewBigInteger(big.NewInt(int64(ic.Block.Index))),
	}))

	return stackitem.NewBool(true)
}

// executeLawAmendment calls Lex to ratify a law amendment.
func (e *Eligere) executeLawAmendment(ic *interop.Context, proposal *state.Proposal) {
	// The Lex contract's ratifyAmendment method will be called
	// This creates or updates a law based on the proposal
	if e.Lex != nil {
		// All amendments create Federal-level laws
		// Constitutional rights (RightLife, RightLiberty, etc.) are immutable in code
		// Constitutional proposals require 67% supermajority but still create Federal laws
		lawCategory := state.LawCategoryFederal

		// Call Lex to ratify the amendment
		e.Lex.RatifyAmendmentInternal(ic, proposal.ID, proposal.ContentHash, lawCategory, 0)
	}
}

// ============ Query Methods ============

func (e *Eligere) getProposal(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	proposalID := toBigInt(args[0]).Uint64()

	proposal := e.getProposalInternal(ic.DAO, proposalID)
	if proposal == nil {
		return stackitem.Null{}
	}

	item, err := proposal.ToStackItem()
	if err != nil {
		return stackitem.Null{}
	}
	return item
}

func (e *Eligere) hasVoted(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	proposalID := toBigInt(args[0]).Uint64()
	voter := toUint160(args[1])

	// Get voter's Vita token
	token, err := e.Vita.GetTokenByOwner(ic.DAO, voter)
	if err != nil || token == nil {
		return stackitem.NewBool(false)
	}

	return stackitem.NewBool(e.hasVotedInternal(ic.DAO, proposalID, token.TokenID))
}

func (e *Eligere) getVote(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	proposalID := toBigInt(args[0]).Uint64()
	voter := toUint160(args[1])

	// Get voter's Vita token
	token, err := e.Vita.GetTokenByOwner(ic.DAO, voter)
	if err != nil || token == nil {
		return stackitem.Null{}
	}

	vote := e.getVoteInternal(ic.DAO, proposalID, token.TokenID)
	if vote == nil {
		return stackitem.Null{}
	}

	item, err := vote.ToStackItem()
	if err != nil {
		return stackitem.Null{}
	}
	return item
}

func (e *Eligere) getProposalCount(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	key := []byte{eligerePrefixProposalCounter}
	count := getIntWithKey(e.ID, ic.DAO, key)
	return stackitem.NewBigInteger(big.NewInt(count))
}

func (e *Eligere) getConfig(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	cfg := e.getConfigInternal(ic.DAO)
	item, err := cfg.ToStackItem()
	if err != nil {
		return stackitem.Null{}
	}
	return item
}
