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

// Sese represents the life planning native contract.
type Sese struct {
	interop.ContractMD
	Tutus ITutus
	Vita         IVita
	RoleRegistry IRoleRegistry
	Lex          ILex
}

// SeseCache represents the cached state for Sese contract.
type SeseCache struct {
	accountCount    uint64
	careerCount     uint64
	sabbaticalCount uint64
	milestoneCount  uint64
	goalCount       uint64
}

// Storage key prefixes for Sese.
const (
	sesePrefixAccount             byte = 0x01 // vitaID -> LifePlanAccount
	sesePrefixAccountByOwner      byte = 0x02 // owner -> vitaID
	sesePrefixCareer              byte = 0x10 // careerID -> CareerCycle
	sesePrefixCareerByOwner       byte = 0x11 // vitaID + careerID -> exists
	sesePrefixActiveCareer        byte = 0x12 // vitaID -> active careerID
	sesePrefixSabbatical          byte = 0x20 // sabbaticalID -> Sabbatical
	sesePrefixSabbaticalByOwner   byte = 0x21 // vitaID + sabbaticalID -> exists
	sesePrefixActiveSabbatical    byte = 0x22 // vitaID -> active sabbaticalID
	sesePrefixMilestone           byte = 0x30 // milestoneID -> LifeMilestone
	sesePrefixMilestoneByOwner    byte = 0x31 // vitaID + milestoneID -> exists
	sesePrefixMilestoneByType     byte = 0x32 // vitaID + type + milestoneID -> exists
	sesePrefixGoal                byte = 0x40 // goalID -> LifeGoal
	sesePrefixGoalByOwner         byte = 0x41 // vitaID + goalID -> exists
	sesePrefixActiveGoals         byte = 0x42 // vitaID + goalID -> exists (active only)
	sesePrefixAccountCounter      byte = 0xF0 // -> uint64
	sesePrefixCareerCounter       byte = 0xF1 // -> next career ID
	sesePrefixSabbaticalCounter   byte = 0xF2 // -> next sabbatical ID
	sesePrefixMilestoneCounter    byte = 0xF3 // -> next milestone ID
	sesePrefixGoalCounter         byte = 0xF4 // -> next goal ID
	sesePrefixConfig              byte = 0xFF // -> SeseConfig
)

// Event names for Sese.
const (
	LifePlanActivatedEvent    = "LifePlanActivated"
	ContributionMadeEvent     = "ContributionMade"
	CareerStartedEvent        = "CareerStarted"
	CareerEndedEvent          = "CareerEnded"
	SabbaticalStartedEvent    = "SabbaticalStarted"
	SabbaticalCompletedEvent  = "SabbaticalCompleted"
	SabbaticalCancelledEvent  = "SabbaticalCancelled"
	MilestoneRecordedEvent    = "MilestoneRecorded"
	MilestoneVerifiedEvent    = "MilestoneVerified"
	GoalCreatedEvent          = "GoalCreated"
	GoalUpdatedEvent          = "GoalUpdated"
	GoalCompletedEvent        = "GoalCompleted"
)

// Role constants for life planners.
const (
	RoleLifePlanner uint64 = 22 // Can verify milestones and manage career transitions
)

// Various errors for Sese.
var (
	ErrSeseAccountNotFound        = errors.New("life plan account not found")
	ErrSeseAccountExists          = errors.New("life plan account already exists")
	ErrSeseAccountSuspended       = errors.New("life plan account is suspended")
	ErrSeseAccountClosed          = errors.New("life plan account is closed")
	ErrSeseNoVita                 = errors.New("owner must have an active Vita")
	ErrSeseInsufficientBalance    = errors.New("insufficient account balance")
	ErrSeseInsufficientCredits    = errors.New("insufficient sabbatical credits")
	ErrSeseInvalidAmount          = errors.New("invalid amount")
	ErrSeseCareerNotFound         = errors.New("career cycle not found")
	ErrSeseCareerExists           = errors.New("active career already exists")
	ErrSeseCareerEnded            = errors.New("career already ended")
	ErrSeseSabbaticalNotFound     = errors.New("sabbatical not found")
	ErrSeseSabbaticalExists       = errors.New("active sabbatical already exists")
	ErrSeseSabbaticalEnded        = errors.New("sabbatical already ended")
	ErrSeseSabbaticalTooShort     = errors.New("sabbatical duration too short")
	ErrSeseSabbaticalTooLong      = errors.New("sabbatical duration too long")
	ErrSeseMilestoneNotFound      = errors.New("milestone not found")
	ErrSeseGoalNotFound           = errors.New("goal not found")
	ErrSeseGoalCompleted          = errors.New("goal already completed")
	ErrSeseNotCommittee           = errors.New("invalid committee signature")
	ErrSeseNotOwner               = errors.New("caller is not the owner")
	ErrSeseNotLifePlanner         = errors.New("caller is not an authorized life planner")
	ErrSeseLaborRestricted        = errors.New("labor right is restricted")
	ErrSeseInvalidField           = errors.New("invalid career field")
	ErrSeseInvalidReason          = errors.New("invalid reason")
	ErrSeseInvalidTitle           = errors.New("invalid title")
	ErrSeseInvalidDescription     = errors.New("invalid description")
	ErrSeseInvalidProgress        = errors.New("invalid progress value")
)

var (
	_ interop.Contract        = (*Sese)(nil)
	_ dao.NativeContractCache = (*SeseCache)(nil)
)

// Copy implements NativeContractCache interface.
func (c *SeseCache) Copy() dao.NativeContractCache {
	return &SeseCache{
		accountCount:    c.accountCount,
		careerCount:     c.careerCount,
		sabbaticalCount: c.sabbaticalCount,
		milestoneCount:  c.milestoneCount,
		goalCount:       c.goalCount,
	}
}

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

// checkLifePlanner checks if the caller has life planner authority.
func (s *Sese) checkLifePlanner(ic *interop.Context) bool {
	caller := ic.VM.GetCallingScriptHash()
	if s.RoleRegistry != nil {
		if s.RoleRegistry.HasRoleInternal(ic.DAO, caller, RoleLifePlanner, ic.Block.Index) {
			return true
		}
	}
	// Committee members can also act as life planners
	return s.checkCommittee(ic)
}

// checkLaborRight checks if subject has labor rights via Lex.
func (s *Sese) checkLaborRight(ic *interop.Context, subject util.Uint160) bool {
	if s.Lex == nil {
		return true // Allow if Lex not available
	}
	return s.Lex.HasRightInternal(ic.DAO, subject, state.RightLabor, ic.Block.Index)
}

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

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

	// activateLifePlan - Activate life planning account for a Vita holder
	desc := NewDescriptor("activateLifePlan", smartcontract.BoolType,
		manifest.NewParameter("owner", smartcontract.Hash160Type))
	md := NewMethodAndPrice(s.activateLifePlan, 1<<17, callflag.States|callflag.AllowNotify)
	s.AddMethod(md, desc)

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

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

	// contribute - Make a contribution to life plan
	desc = NewDescriptor("contribute", smartcontract.BoolType,
		manifest.NewParameter("owner", smartcontract.Hash160Type),
		manifest.NewParameter("amount", smartcontract.IntegerType),
		manifest.NewParameter("isEmployer", smartcontract.BoolType))
	md = NewMethodAndPrice(s.contribute, 1<<16, callflag.States|callflag.AllowNotify)
	s.AddMethod(md, desc)

	// allocateSabbaticalCredits - Allocate sabbatical credits (committee only)
	desc = NewDescriptor("allocateSabbaticalCredits", smartcontract.BoolType,
		manifest.NewParameter("owner", smartcontract.Hash160Type),
		manifest.NewParameter("amount", smartcontract.IntegerType),
		manifest.NewParameter("reason", smartcontract.StringType))
	md = NewMethodAndPrice(s.allocateSabbaticalCredits, 1<<16, callflag.States|callflag.AllowNotify)
	s.AddMethod(md, desc)

	// getBalance - Get current balance
	desc = NewDescriptor("getBalance", smartcontract.IntegerType,
		manifest.NewParameter("owner", smartcontract.Hash160Type))
	md = NewMethodAndPrice(s.getBalance, 1<<15, callflag.ReadStates)
	s.AddMethod(md, desc)

	// getSabbaticalCredits - Get available sabbatical credits
	desc = NewDescriptor("getSabbaticalCredits", smartcontract.IntegerType,
		manifest.NewParameter("owner", smartcontract.Hash160Type))
	md = NewMethodAndPrice(s.getSabbaticalCredits, 1<<15, callflag.ReadStates)
	s.AddMethod(md, desc)

	// ===== Career Management =====

	// startCareer - Start a new career cycle
	desc = NewDescriptor("startCareer", smartcontract.IntegerType,
		manifest.NewParameter("owner", smartcontract.Hash160Type),
		manifest.NewParameter("careerField", smartcontract.StringType))
	md = NewMethodAndPrice(s.startCareer, 1<<17, callflag.States|callflag.AllowNotify)
	s.AddMethod(md, desc)

	// endCareer - End current career cycle
	desc = NewDescriptor("endCareer", smartcontract.BoolType,
		manifest.NewParameter("careerID", smartcontract.IntegerType),
		manifest.NewParameter("reason", smartcontract.StringType))
	md = NewMethodAndPrice(s.endCareer, 1<<16, callflag.States|callflag.AllowNotify)
	s.AddMethod(md, desc)

	// getCareer - Get career cycle details
	desc = NewDescriptor("getCareer", smartcontract.ArrayType,
		manifest.NewParameter("careerID", smartcontract.IntegerType))
	md = NewMethodAndPrice(s.getCareer, 1<<15, callflag.ReadStates)
	s.AddMethod(md, desc)

	// getActiveCareer - Get owner's active career
	desc = NewDescriptor("getActiveCareer", smartcontract.ArrayType,
		manifest.NewParameter("owner", smartcontract.Hash160Type))
	md = NewMethodAndPrice(s.getActiveCareer, 1<<15, callflag.ReadStates)
	s.AddMethod(md, desc)

	// ===== Sabbatical Management =====

	// startSabbatical - Start a sabbatical
	desc = NewDescriptor("startSabbatical", smartcontract.IntegerType,
		manifest.NewParameter("owner", smartcontract.Hash160Type),
		manifest.NewParameter("purpose", smartcontract.IntegerType),
		manifest.NewParameter("description", smartcontract.StringType),
		manifest.NewParameter("duration", smartcontract.IntegerType))
	md = NewMethodAndPrice(s.startSabbatical, 1<<17, callflag.States|callflag.AllowNotify)
	s.AddMethod(md, desc)

	// completeSabbatical - Complete a sabbatical
	desc = NewDescriptor("completeSabbatical", smartcontract.BoolType,
		manifest.NewParameter("sabbaticalID", smartcontract.IntegerType),
		manifest.NewParameter("outcome", smartcontract.StringType))
	md = NewMethodAndPrice(s.completeSabbatical, 1<<16, callflag.States|callflag.AllowNotify)
	s.AddMethod(md, desc)

	// cancelSabbatical - Cancel a sabbatical
	desc = NewDescriptor("cancelSabbatical", smartcontract.BoolType,
		manifest.NewParameter("sabbaticalID", smartcontract.IntegerType),
		manifest.NewParameter("reason", smartcontract.StringType))
	md = NewMethodAndPrice(s.cancelSabbatical, 1<<16, callflag.States|callflag.AllowNotify)
	s.AddMethod(md, desc)

	// getSabbatical - Get sabbatical details
	desc = NewDescriptor("getSabbatical", smartcontract.ArrayType,
		manifest.NewParameter("sabbaticalID", smartcontract.IntegerType))
	md = NewMethodAndPrice(s.getSabbatical, 1<<15, callflag.ReadStates)
	s.AddMethod(md, desc)

	// getActiveSabbatical - Get owner's active sabbatical
	desc = NewDescriptor("getActiveSabbatical", smartcontract.ArrayType,
		manifest.NewParameter("owner", smartcontract.Hash160Type))
	md = NewMethodAndPrice(s.getActiveSabbatical, 1<<15, callflag.ReadStates)
	s.AddMethod(md, desc)

	// ===== Milestone Management =====

	// recordMilestone - Record a life milestone
	desc = NewDescriptor("recordMilestone", smartcontract.IntegerType,
		manifest.NewParameter("owner", smartcontract.Hash160Type),
		manifest.NewParameter("milestoneType", smartcontract.IntegerType),
		manifest.NewParameter("title", smartcontract.StringType),
		manifest.NewParameter("description", smartcontract.StringType),
		manifest.NewParameter("contentHash", smartcontract.Hash256Type))
	md = NewMethodAndPrice(s.recordMilestone, 1<<17, callflag.States|callflag.AllowNotify)
	s.AddMethod(md, desc)

	// verifyMilestone - Verify a milestone (life planner only)
	desc = NewDescriptor("verifyMilestone", smartcontract.BoolType,
		manifest.NewParameter("milestoneID", smartcontract.IntegerType))
	md = NewMethodAndPrice(s.verifyMilestone, 1<<16, callflag.States|callflag.AllowNotify)
	s.AddMethod(md, desc)

	// getMilestone - Get milestone details
	desc = NewDescriptor("getMilestone", smartcontract.ArrayType,
		manifest.NewParameter("milestoneID", smartcontract.IntegerType))
	md = NewMethodAndPrice(s.getMilestone, 1<<15, callflag.ReadStates)
	s.AddMethod(md, desc)

	// ===== Goal Management =====

	// createGoal - Create a life goal
	desc = NewDescriptor("createGoal", smartcontract.IntegerType,
		manifest.NewParameter("owner", smartcontract.Hash160Type),
		manifest.NewParameter("title", smartcontract.StringType),
		manifest.NewParameter("description", smartcontract.StringType),
		manifest.NewParameter("targetBlock", smartcontract.IntegerType))
	md = NewMethodAndPrice(s.createGoal, 1<<17, callflag.States|callflag.AllowNotify)
	s.AddMethod(md, desc)

	// updateGoalProgress - Update goal progress
	desc = NewDescriptor("updateGoalProgress", smartcontract.BoolType,
		manifest.NewParameter("goalID", smartcontract.IntegerType),
		manifest.NewParameter("progress", smartcontract.IntegerType))
	md = NewMethodAndPrice(s.updateGoalProgress, 1<<16, callflag.States|callflag.AllowNotify)
	s.AddMethod(md, desc)

	// completeGoal - Mark goal as completed
	desc = NewDescriptor("completeGoal", smartcontract.BoolType,
		manifest.NewParameter("goalID", smartcontract.IntegerType))
	md = NewMethodAndPrice(s.completeGoal, 1<<16, callflag.States|callflag.AllowNotify)
	s.AddMethod(md, desc)

	// abandonGoal - Abandon a goal
	desc = NewDescriptor("abandonGoal", smartcontract.BoolType,
		manifest.NewParameter("goalID", smartcontract.IntegerType),
		manifest.NewParameter("reason", smartcontract.StringType))
	md = NewMethodAndPrice(s.abandonGoal, 1<<16, callflag.States|callflag.AllowNotify)
	s.AddMethod(md, desc)

	// getGoal - Get goal details
	desc = NewDescriptor("getGoal", smartcontract.ArrayType,
		manifest.NewParameter("goalID", smartcontract.IntegerType))
	md = NewMethodAndPrice(s.getGoal, 1<<15, callflag.ReadStates)
	s.AddMethod(md, desc)

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

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

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

	// getTotalCareers - Get total career cycles
	desc = NewDescriptor("getTotalCareers", smartcontract.IntegerType)
	md = NewMethodAndPrice(s.getTotalCareers, 1<<15, callflag.ReadStates)
	s.AddMethod(md, desc)

	// getTotalSabbaticals - Get total sabbaticals
	desc = NewDescriptor("getTotalSabbaticals", smartcontract.IntegerType)
	md = NewMethodAndPrice(s.getTotalSabbaticals, 1<<15, callflag.ReadStates)
	s.AddMethod(md, desc)

	// getTotalMilestones - Get total milestones
	desc = NewDescriptor("getTotalMilestones", smartcontract.IntegerType)
	md = NewMethodAndPrice(s.getTotalMilestones, 1<<15, callflag.ReadStates)
	s.AddMethod(md, desc)

	// getTotalGoals - Get total goals
	desc = NewDescriptor("getTotalGoals", smartcontract.IntegerType)
	md = NewMethodAndPrice(s.getTotalGoals, 1<<15, callflag.ReadStates)
	s.AddMethod(md, desc)

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

	// LifePlanActivated event
	eDesc := NewEventDescriptor(LifePlanActivatedEvent,
		manifest.NewParameter("vitaID", smartcontract.IntegerType),
		manifest.NewParameter("owner", smartcontract.Hash160Type))
	s.AddEvent(NewEvent(eDesc))

	// ContributionMade event
	eDesc = NewEventDescriptor(ContributionMadeEvent,
		manifest.NewParameter("vitaID", smartcontract.IntegerType),
		manifest.NewParameter("amount", smartcontract.IntegerType),
		manifest.NewParameter("isEmployer", smartcontract.BoolType),
		manifest.NewParameter("balance", smartcontract.IntegerType))
	s.AddEvent(NewEvent(eDesc))

	// CareerStarted event
	eDesc = NewEventDescriptor(CareerStartedEvent,
		manifest.NewParameter("careerID", smartcontract.IntegerType),
		manifest.NewParameter("vitaID", smartcontract.IntegerType),
		manifest.NewParameter("careerField", smartcontract.StringType))
	s.AddEvent(NewEvent(eDesc))

	// CareerEnded event
	eDesc = NewEventDescriptor(CareerEndedEvent,
		manifest.NewParameter("careerID", smartcontract.IntegerType),
		manifest.NewParameter("reason", smartcontract.StringType))
	s.AddEvent(NewEvent(eDesc))

	// SabbaticalStarted event
	eDesc = NewEventDescriptor(SabbaticalStartedEvent,
		manifest.NewParameter("sabbaticalID", smartcontract.IntegerType),
		manifest.NewParameter("vitaID", smartcontract.IntegerType),
		manifest.NewParameter("purpose", smartcontract.IntegerType))
	s.AddEvent(NewEvent(eDesc))

	// SabbaticalCompleted event
	eDesc = NewEventDescriptor(SabbaticalCompletedEvent,
		manifest.NewParameter("sabbaticalID", smartcontract.IntegerType))
	s.AddEvent(NewEvent(eDesc))

	// SabbaticalCancelled event
	eDesc = NewEventDescriptor(SabbaticalCancelledEvent,
		manifest.NewParameter("sabbaticalID", smartcontract.IntegerType),
		manifest.NewParameter("reason", smartcontract.StringType))
	s.AddEvent(NewEvent(eDesc))

	// MilestoneRecorded event
	eDesc = NewEventDescriptor(MilestoneRecordedEvent,
		manifest.NewParameter("milestoneID", smartcontract.IntegerType),
		manifest.NewParameter("vitaID", smartcontract.IntegerType),
		manifest.NewParameter("milestoneType", smartcontract.IntegerType))
	s.AddEvent(NewEvent(eDesc))

	// MilestoneVerified event
	eDesc = NewEventDescriptor(MilestoneVerifiedEvent,
		manifest.NewParameter("milestoneID", smartcontract.IntegerType))
	s.AddEvent(NewEvent(eDesc))

	// GoalCreated event
	eDesc = NewEventDescriptor(GoalCreatedEvent,
		manifest.NewParameter("goalID", smartcontract.IntegerType),
		manifest.NewParameter("vitaID", smartcontract.IntegerType),
		manifest.NewParameter("title", smartcontract.StringType))
	s.AddEvent(NewEvent(eDesc))

	// GoalUpdated event
	eDesc = NewEventDescriptor(GoalUpdatedEvent,
		manifest.NewParameter("goalID", smartcontract.IntegerType),
		manifest.NewParameter("progress", smartcontract.IntegerType))
	s.AddEvent(NewEvent(eDesc))

	// GoalCompleted event
	eDesc = NewEventDescriptor(GoalCompletedEvent,
		manifest.NewParameter("goalID", smartcontract.IntegerType))
	s.AddEvent(NewEvent(eDesc))

	return s
}

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

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

	// Initialize counters
	s.setAccountCounter(ic.DAO, 0)
	s.setCareerCounter(ic.DAO, 0)
	s.setSabbaticalCounter(ic.DAO, 0)
	s.setMilestoneCounter(ic.DAO, 0)
	s.setGoalCounter(ic.DAO, 0)

	// Initialize config with defaults
	cfg := &state.SeseConfig{
		DefaultSabbaticalCredits: 5000,     // 5000 credits per year
		MinSabbaticalDuration:    2592000,  // ~30 days (1-second blocks)
		MaxSabbaticalDuration:    31536000, // ~365 days (1-second blocks)
		GovernmentMatchPercent:   5000,     // 50% match (basis points)
		LongevityBonusPercent:    100,      // 1% per year (basis points)
	}
	s.setConfig(ic.DAO, cfg)

	// Initialize cache
	cache := &SeseCache{
		accountCount:    0,
		careerCount:     0,
		sabbaticalCount: 0,
		milestoneCount:  0,
		goalCount:       0,
	}
	ic.DAO.SetCache(s.ID, cache)

	return nil
}

// InitializeCache initializes the cache from storage.
func (s *Sese) InitializeCache(_ interop.IsHardforkEnabled, blockHeight uint32, d *dao.Simple) error {
	cache := &SeseCache{
		accountCount:    s.getAccountCounter(d),
		careerCount:     s.getCareerCounter(d),
		sabbaticalCount: s.getSabbaticalCounter(d),
		milestoneCount:  s.getMilestoneCounter(d),
		goalCount:       s.getGoalCounter(d),
	}
	d.SetCache(s.ID, cache)
	return nil
}

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

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

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

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

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

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

func (s *Sese) makeCareerKey(careerID uint64) []byte {
	key := make([]byte, 9)
	key[0] = sesePrefixCareer
	binary.BigEndian.PutUint64(key[1:], careerID)
	return key
}

func (s *Sese) makeActiveCareerKey(vitaID uint64) []byte {
	key := make([]byte, 9)
	key[0] = sesePrefixActiveCareer
	binary.BigEndian.PutUint64(key[1:], vitaID)
	return key
}

func (s *Sese) makeSabbaticalKey(sabbaticalID uint64) []byte {
	key := make([]byte, 9)
	key[0] = sesePrefixSabbatical
	binary.BigEndian.PutUint64(key[1:], sabbaticalID)
	return key
}

func (s *Sese) makeActiveSabbaticalKey(vitaID uint64) []byte {
	key := make([]byte, 9)
	key[0] = sesePrefixActiveSabbatical
	binary.BigEndian.PutUint64(key[1:], vitaID)
	return key
}

func (s *Sese) makeMilestoneKey(milestoneID uint64) []byte {
	key := make([]byte, 9)
	key[0] = sesePrefixMilestone
	binary.BigEndian.PutUint64(key[1:], milestoneID)
	return key
}

func (s *Sese) makeGoalKey(goalID uint64) []byte {
	key := make([]byte, 9)
	key[0] = sesePrefixGoal
	binary.BigEndian.PutUint64(key[1:], goalID)
	return key
}

// Counter getters/setters
func (s *Sese) getAccountCounter(d *dao.Simple) uint64 {
	si := d.GetStorageItem(s.ID, []byte{sesePrefixAccountCounter})
	if si == nil {
		return 0
	}
	return binary.BigEndian.Uint64(si)
}

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

func (s *Sese) getCareerCounter(d *dao.Simple) uint64 {
	si := d.GetStorageItem(s.ID, []byte{sesePrefixCareerCounter})
	if si == nil {
		return 0
	}
	return binary.BigEndian.Uint64(si)
}

func (s *Sese) setCareerCounter(d *dao.Simple, count uint64) {
	buf := make([]byte, 8)
	binary.BigEndian.PutUint64(buf, count)
	d.PutStorageItem(s.ID, []byte{sesePrefixCareerCounter}, buf)
}

func (s *Sese) getSabbaticalCounter(d *dao.Simple) uint64 {
	si := d.GetStorageItem(s.ID, []byte{sesePrefixSabbaticalCounter})
	if si == nil {
		return 0
	}
	return binary.BigEndian.Uint64(si)
}

func (s *Sese) setSabbaticalCounter(d *dao.Simple, count uint64) {
	buf := make([]byte, 8)
	binary.BigEndian.PutUint64(buf, count)
	d.PutStorageItem(s.ID, []byte{sesePrefixSabbaticalCounter}, buf)
}

func (s *Sese) getMilestoneCounter(d *dao.Simple) uint64 {
	si := d.GetStorageItem(s.ID, []byte{sesePrefixMilestoneCounter})
	if si == nil {
		return 0
	}
	return binary.BigEndian.Uint64(si)
}

func (s *Sese) setMilestoneCounter(d *dao.Simple, count uint64) {
	buf := make([]byte, 8)
	binary.BigEndian.PutUint64(buf, count)
	d.PutStorageItem(s.ID, []byte{sesePrefixMilestoneCounter}, buf)
}

func (s *Sese) getGoalCounter(d *dao.Simple) uint64 {
	si := d.GetStorageItem(s.ID, []byte{sesePrefixGoalCounter})
	if si == nil {
		return 0
	}
	return binary.BigEndian.Uint64(si)
}

func (s *Sese) setGoalCounter(d *dao.Simple, count uint64) {
	buf := make([]byte, 8)
	binary.BigEndian.PutUint64(buf, count)
	d.PutStorageItem(s.ID, []byte{sesePrefixGoalCounter}, buf)
}

// Config getter/setter
func (s *Sese) getConfigInternal(d *dao.Simple) *state.SeseConfig {
	si := d.GetStorageItem(s.ID, []byte{sesePrefixConfig})
	if si == nil {
		return &state.SeseConfig{
			DefaultSabbaticalCredits: 5000,
			MinSabbaticalDuration:    2592000,
			MaxSabbaticalDuration:    31536000,
			GovernmentMatchPercent:   5000,
			LongevityBonusPercent:    100,
		}
	}
	cfg := new(state.SeseConfig)
	item, _ := stackitem.Deserialize(si)
	cfg.FromStackItem(item)
	return cfg
}

func (s *Sese) setConfig(d *dao.Simple, cfg *state.SeseConfig) {
	item, _ := cfg.ToStackItem()
	data, _ := stackitem.Serialize(item)
	d.PutStorageItem(s.ID, []byte{sesePrefixConfig}, data)
}

// Account storage
func (s *Sese) getAccountInternal(d *dao.Simple, vitaID uint64) *state.LifePlanAccount {
	si := d.GetStorageItem(s.ID, s.makeAccountKey(vitaID))
	if si == nil {
		return nil
	}
	acc := new(state.LifePlanAccount)
	item, _ := stackitem.Deserialize(si)
	acc.FromStackItem(item)
	return acc
}

func (s *Sese) putAccount(d *dao.Simple, acc *state.LifePlanAccount) {
	item, _ := acc.ToStackItem()
	data, _ := stackitem.Serialize(item)
	d.PutStorageItem(s.ID, s.makeAccountKey(acc.VitaID), data)
}

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

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

// Career storage
func (s *Sese) getCareerInternal(d *dao.Simple, careerID uint64) *state.CareerCycle {
	si := d.GetStorageItem(s.ID, s.makeCareerKey(careerID))
	if si == nil {
		return nil
	}
	career := new(state.CareerCycle)
	item, _ := stackitem.Deserialize(si)
	career.FromStackItem(item)
	return career
}

func (s *Sese) putCareer(d *dao.Simple, career *state.CareerCycle) {
	item, _ := career.ToStackItem()
	data, _ := stackitem.Serialize(item)
	d.PutStorageItem(s.ID, s.makeCareerKey(career.ID), data)
}

func (s *Sese) getActiveCareerID(d *dao.Simple, vitaID uint64) (uint64, bool) {
	si := d.GetStorageItem(s.ID, s.makeActiveCareerKey(vitaID))
	if si == nil {
		return 0, false
	}
	return binary.BigEndian.Uint64(si), true
}

func (s *Sese) setActiveCareerID(d *dao.Simple, vitaID uint64, careerID uint64) {
	buf := make([]byte, 8)
	binary.BigEndian.PutUint64(buf, careerID)
	d.PutStorageItem(s.ID, s.makeActiveCareerKey(vitaID), buf)
}

func (s *Sese) clearActiveCareer(d *dao.Simple, vitaID uint64) {
	d.DeleteStorageItem(s.ID, s.makeActiveCareerKey(vitaID))
}

// Sabbatical storage
func (s *Sese) getSabbaticalInternal(d *dao.Simple, sabbaticalID uint64) *state.Sabbatical {
	si := d.GetStorageItem(s.ID, s.makeSabbaticalKey(sabbaticalID))
	if si == nil {
		return nil
	}
	sabbatical := new(state.Sabbatical)
	item, _ := stackitem.Deserialize(si)
	sabbatical.FromStackItem(item)
	return sabbatical
}

func (s *Sese) putSabbatical(d *dao.Simple, sabbatical *state.Sabbatical) {
	item, _ := sabbatical.ToStackItem()
	data, _ := stackitem.Serialize(item)
	d.PutStorageItem(s.ID, s.makeSabbaticalKey(sabbatical.ID), data)
}

func (s *Sese) getActiveSabbaticalID(d *dao.Simple, vitaID uint64) (uint64, bool) {
	si := d.GetStorageItem(s.ID, s.makeActiveSabbaticalKey(vitaID))
	if si == nil {
		return 0, false
	}
	return binary.BigEndian.Uint64(si), true
}

func (s *Sese) setActiveSabbaticalID(d *dao.Simple, vitaID uint64, sabbaticalID uint64) {
	buf := make([]byte, 8)
	binary.BigEndian.PutUint64(buf, sabbaticalID)
	d.PutStorageItem(s.ID, s.makeActiveSabbaticalKey(vitaID), buf)
}

func (s *Sese) clearActiveSabbatical(d *dao.Simple, vitaID uint64) {
	d.DeleteStorageItem(s.ID, s.makeActiveSabbaticalKey(vitaID))
}

// Milestone storage
func (s *Sese) getMilestoneInternal(d *dao.Simple, milestoneID uint64) *state.LifeMilestone {
	si := d.GetStorageItem(s.ID, s.makeMilestoneKey(milestoneID))
	if si == nil {
		return nil
	}
	milestone := new(state.LifeMilestone)
	item, _ := stackitem.Deserialize(si)
	milestone.FromStackItem(item)
	return milestone
}

func (s *Sese) putMilestone(d *dao.Simple, milestone *state.LifeMilestone) {
	item, _ := milestone.ToStackItem()
	data, _ := stackitem.Serialize(item)
	d.PutStorageItem(s.ID, s.makeMilestoneKey(milestone.ID), data)
}

// Goal storage
func (s *Sese) getGoalInternal(d *dao.Simple, goalID uint64) *state.LifeGoal {
	si := d.GetStorageItem(s.ID, s.makeGoalKey(goalID))
	if si == nil {
		return nil
	}
	goal := new(state.LifeGoal)
	item, _ := stackitem.Deserialize(si)
	goal.FromStackItem(item)
	return goal
}

func (s *Sese) putGoal(d *dao.Simple, goal *state.LifeGoal) {
	item, _ := goal.ToStackItem()
	data, _ := stackitem.Serialize(item)
	d.PutStorageItem(s.ID, s.makeGoalKey(goal.ID), data)
}

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

// activateLifePlan activates life planning account for a Vita holder.
func (s *Sese) activateLifePlan(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	owner := toUint160(args[0])

	// Check owner has active Vita
	if s.Vita == nil {
		panic(ErrSeseNoVita)
	}
	vita, err := s.Vita.GetTokenByOwner(ic.DAO, owner)
	if err != nil || vita == nil {
		panic(ErrSeseNoVita)
	}
	if vita.Status != state.TokenStatusActive {
		panic(ErrSeseNoVita)
	}

	// Check if account already exists
	existing := s.getAccountInternal(ic.DAO, vita.TokenID)
	if existing != nil {
		panic(ErrSeseAccountExists)
	}

	// Check labor rights
	if !s.checkLaborRight(ic, owner) {
		// Log but allow (EnforcementLogging)
	}

	// Get cache and increment counter
	cache := ic.DAO.GetRWCache(s.ID).(*SeseCache)
	cache.accountCount++
	s.setAccountCounter(ic.DAO, cache.accountCount)

	// Get default credits from config
	cfg := s.getConfigInternal(ic.DAO)

	// Create account
	acc := &state.LifePlanAccount{
		VitaID:                  vita.TokenID,
		Owner:                   owner,
		TotalContributions:      0,
		EmployerContributions:   0,
		GovernmentContributions: 0,
		CurrentBalance:          0,
		SabbaticalCredits:       cfg.DefaultSabbaticalCredits,
		LongevityMultiplier:     10000, // 100% = 10000 basis points
		CurrentCareerCycleID:    0,
		TotalCareerCycles:       0,
		TotalSabbaticals:        0,
		Status:                  state.LifePlanAccountActive,
		CreatedAt:               ic.Block.Index,
		UpdatedAt:               ic.Block.Index,
	}

	// Store account
	s.putAccount(ic.DAO, acc)
	s.setOwnerToVitaID(ic.DAO, owner, vita.TokenID)

	// Emit event
	ic.AddNotification(s.Hash, LifePlanActivatedEvent, stackitem.NewArray([]stackitem.Item{
		stackitem.NewBigInteger(big.NewInt(int64(vita.TokenID))),
		stackitem.NewByteArray(owner.BytesBE()),
	}))

	return stackitem.NewBool(true)
}

// getAccount returns life plan account by owner.
func (s *Sese) getAccount(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	owner := toUint160(args[0])

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

	acc := s.getAccountInternal(ic.DAO, vitaID)
	if acc == nil {
		return stackitem.Null{}
	}

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

// getAccountByVitaID returns life plan account by Vita ID.
func (s *Sese) getAccountByVitaID(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	vitaID := toUint64(args[0])

	acc := s.getAccountInternal(ic.DAO, vitaID)
	if acc == nil {
		return stackitem.Null{}
	}

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

// contribute makes a contribution to life plan.
func (s *Sese) contribute(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	owner := toUint160(args[0])
	amount := toUint64(args[1])
	isEmployer := toBool(args[2])

	if amount == 0 {
		panic(ErrSeseInvalidAmount)
	}

	// Get or auto-create account
	vitaID, found := s.getVitaIDByOwner(ic.DAO, owner)
	if !found {
		// Auto-create if Vita exists
		if s.Vita == nil {
			panic(ErrSeseNoVita)
		}
		vita, err := s.Vita.GetTokenByOwner(ic.DAO, owner)
		if err != nil || vita == nil || vita.Status != state.TokenStatusActive {
			panic(ErrSeseNoVita)
		}
		vitaID = vita.TokenID

		cfg := s.getConfigInternal(ic.DAO)

		// Create account
		cache := ic.DAO.GetRWCache(s.ID).(*SeseCache)
		cache.accountCount++
		s.setAccountCounter(ic.DAO, cache.accountCount)

		acc := &state.LifePlanAccount{
			VitaID:                  vitaID,
			Owner:                   owner,
			TotalContributions:      0,
			EmployerContributions:   0,
			GovernmentContributions: 0,
			CurrentBalance:          0,
			SabbaticalCredits:       cfg.DefaultSabbaticalCredits,
			LongevityMultiplier:     10000,
			CurrentCareerCycleID:    0,
			TotalCareerCycles:       0,
			TotalSabbaticals:        0,
			Status:                  state.LifePlanAccountActive,
			CreatedAt:               ic.Block.Index,
			UpdatedAt:               ic.Block.Index,
		}
		s.putAccount(ic.DAO, acc)
		s.setOwnerToVitaID(ic.DAO, owner, vitaID)

		ic.AddNotification(s.Hash, LifePlanActivatedEvent, stackitem.NewArray([]stackitem.Item{
			stackitem.NewBigInteger(big.NewInt(int64(vitaID))),
			stackitem.NewByteArray(owner.BytesBE()),
		}))
	}

	acc := s.getAccountInternal(ic.DAO, vitaID)
	if acc == nil {
		panic(ErrSeseAccountNotFound)
	}
	if acc.Status != state.LifePlanAccountActive {
		panic(ErrSeseAccountSuspended)
	}

	// Add contribution
	acc.TotalContributions += amount
	acc.CurrentBalance += amount

	if isEmployer {
		acc.EmployerContributions += amount
	}

	// Calculate government match
	cfg := s.getConfigInternal(ic.DAO)
	govMatch := (amount * uint64(cfg.GovernmentMatchPercent)) / 10000
	if govMatch > 0 {
		acc.GovernmentContributions += govMatch
		acc.CurrentBalance += govMatch
	}

	acc.UpdatedAt = ic.Block.Index
	s.putAccount(ic.DAO, acc)

	// Emit event
	ic.AddNotification(s.Hash, ContributionMadeEvent, stackitem.NewArray([]stackitem.Item{
		stackitem.NewBigInteger(big.NewInt(int64(vitaID))),
		stackitem.NewBigInteger(big.NewInt(int64(amount))),
		stackitem.NewBool(isEmployer),
		stackitem.NewBigInteger(big.NewInt(int64(acc.CurrentBalance))),
	}))

	return stackitem.NewBool(true)
}

// allocateSabbaticalCredits allocates sabbatical credits (committee only).
func (s *Sese) allocateSabbaticalCredits(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	owner := toUint160(args[0])
	amount := toUint64(args[1])
	// reason := toString(args[2]) // for logging

	// Committee only
	if !s.checkCommittee(ic) {
		panic(ErrSeseNotCommittee)
	}

	if amount == 0 {
		panic(ErrSeseInvalidAmount)
	}

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

	acc := s.getAccountInternal(ic.DAO, vitaID)
	if acc == nil {
		panic(ErrSeseAccountNotFound)
	}
	if acc.Status != state.LifePlanAccountActive {
		panic(ErrSeseAccountSuspended)
	}

	acc.SabbaticalCredits += amount
	acc.UpdatedAt = ic.Block.Index
	s.putAccount(ic.DAO, acc)

	return stackitem.NewBool(true)
}

// getBalance returns current balance.
func (s *Sese) getBalance(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	owner := toUint160(args[0])

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

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

	return stackitem.NewBigInteger(big.NewInt(int64(acc.CurrentBalance)))
}

// getSabbaticalCredits returns available sabbatical credits.
func (s *Sese) getSabbaticalCredits(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	owner := toUint160(args[0])

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

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

	return stackitem.NewBigInteger(big.NewInt(int64(acc.SabbaticalCredits)))
}

// startCareer starts a new career cycle.
func (s *Sese) startCareer(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	owner := toUint160(args[0])
	careerField := toString(args[1])

	if len(careerField) == 0 || len(careerField) > 128 {
		panic(ErrSeseInvalidField)
	}

	// Get account
	vitaID, found := s.getVitaIDByOwner(ic.DAO, owner)
	if !found {
		panic(ErrSeseAccountNotFound)
	}

	acc := s.getAccountInternal(ic.DAO, vitaID)
	if acc == nil {
		panic(ErrSeseAccountNotFound)
	}
	if acc.Status != state.LifePlanAccountActive {
		panic(ErrSeseAccountSuspended)
	}

	// Check no active career
	_, hasActive := s.getActiveCareerID(ic.DAO, vitaID)
	if hasActive {
		panic(ErrSeseCareerExists)
	}

	// Get next career ID
	cache := ic.DAO.GetRWCache(s.ID).(*SeseCache)
	careerID := cache.careerCount
	cache.careerCount++
	s.setCareerCounter(ic.DAO, cache.careerCount)

	// Create career
	career := &state.CareerCycle{
		ID:                careerID,
		VitaID:            vitaID,
		Owner:             owner,
		CareerField:       careerField,
		StartedAt:         ic.Block.Index,
		EndedAt:           0,
		ContributionTotal: 0,
		TransitionReason:  "",
		Status:            state.CareerCycleActive,
	}

	s.putCareer(ic.DAO, career)
	s.setActiveCareerID(ic.DAO, vitaID, careerID)

	// Update account
	acc.CurrentCareerCycleID = careerID
	acc.TotalCareerCycles++
	acc.UpdatedAt = ic.Block.Index
	s.putAccount(ic.DAO, acc)

	// Emit event
	ic.AddNotification(s.Hash, CareerStartedEvent, stackitem.NewArray([]stackitem.Item{
		stackitem.NewBigInteger(big.NewInt(int64(careerID))),
		stackitem.NewBigInteger(big.NewInt(int64(vitaID))),
		stackitem.NewByteArray([]byte(careerField)),
	}))

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

// endCareer ends current career cycle.
func (s *Sese) endCareer(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	careerID := toUint64(args[0])
	reason := toString(args[1])

	career := s.getCareerInternal(ic.DAO, careerID)
	if career == nil {
		panic(ErrSeseCareerNotFound)
	}
	if career.Status != state.CareerCycleActive {
		panic(ErrSeseCareerEnded)
	}

	// Check caller is owner or committee
	caller := ic.VM.GetCallingScriptHash()
	if caller != career.Owner && !s.checkCommittee(ic) {
		panic(ErrSeseNotOwner)
	}

	// End career
	career.EndedAt = ic.Block.Index
	career.TransitionReason = reason
	career.Status = state.CareerCycleCompleted
	s.putCareer(ic.DAO, career)
	s.clearActiveCareer(ic.DAO, career.VitaID)

	// Update account
	acc := s.getAccountInternal(ic.DAO, career.VitaID)
	if acc != nil {
		acc.CurrentCareerCycleID = 0
		acc.UpdatedAt = ic.Block.Index
		s.putAccount(ic.DAO, acc)
	}

	// Emit event
	ic.AddNotification(s.Hash, CareerEndedEvent, stackitem.NewArray([]stackitem.Item{
		stackitem.NewBigInteger(big.NewInt(int64(careerID))),
		stackitem.NewByteArray([]byte(reason)),
	}))

	return stackitem.NewBool(true)
}

// getCareer returns career cycle details.
func (s *Sese) getCareer(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	careerID := toUint64(args[0])

	career := s.getCareerInternal(ic.DAO, careerID)
	if career == nil {
		return stackitem.Null{}
	}

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

// getActiveCareer returns owner's active career.
func (s *Sese) getActiveCareer(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	owner := toUint160(args[0])

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

	careerID, hasActive := s.getActiveCareerID(ic.DAO, vitaID)
	if !hasActive {
		return stackitem.Null{}
	}

	career := s.getCareerInternal(ic.DAO, careerID)
	if career == nil {
		return stackitem.Null{}
	}

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

// startSabbatical starts a sabbatical.
func (s *Sese) startSabbatical(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	owner := toUint160(args[0])
	purpose := state.SabbaticalPurpose(toUint64(args[1]))
	description := toString(args[2])
	duration := toUint32(args[3])

	if len(description) == 0 || len(description) > 256 {
		panic(ErrSeseInvalidDescription)
	}

	// Get account
	vitaID, found := s.getVitaIDByOwner(ic.DAO, owner)
	if !found {
		panic(ErrSeseAccountNotFound)
	}

	acc := s.getAccountInternal(ic.DAO, vitaID)
	if acc == nil {
		panic(ErrSeseAccountNotFound)
	}
	if acc.Status != state.LifePlanAccountActive {
		panic(ErrSeseAccountSuspended)
	}

	// Check no active sabbatical
	_, hasActive := s.getActiveSabbaticalID(ic.DAO, vitaID)
	if hasActive {
		panic(ErrSeseSabbaticalExists)
	}

	// Check duration limits
	cfg := s.getConfigInternal(ic.DAO)
	if duration < cfg.MinSabbaticalDuration {
		panic(ErrSeseSabbaticalTooShort)
	}
	if duration > cfg.MaxSabbaticalDuration {
		panic(ErrSeseSabbaticalTooLong)
	}

	// Check sufficient credits
	if acc.SabbaticalCredits == 0 {
		panic(ErrSeseInsufficientCredits)
	}

	// Get next sabbatical ID
	cache := ic.DAO.GetRWCache(s.ID).(*SeseCache)
	sabbaticalID := cache.sabbaticalCount
	cache.sabbaticalCount++
	s.setSabbaticalCounter(ic.DAO, cache.sabbaticalCount)

	// Create sabbatical
	sabbatical := &state.Sabbatical{
		ID:          sabbaticalID,
		VitaID:      vitaID,
		Owner:       owner,
		Purpose:     purpose,
		Description: description,
		StartedAt:   ic.Block.Index,
		Duration:    duration,
		EndedAt:     0,
		FundingUsed: 0,
		Outcome:     "",
		Status:      state.SabbaticalActive,
	}

	s.putSabbatical(ic.DAO, sabbatical)
	s.setActiveSabbaticalID(ic.DAO, vitaID, sabbaticalID)

	// Update account
	acc.TotalSabbaticals++
	acc.UpdatedAt = ic.Block.Index
	s.putAccount(ic.DAO, acc)

	// Emit event
	ic.AddNotification(s.Hash, SabbaticalStartedEvent, stackitem.NewArray([]stackitem.Item{
		stackitem.NewBigInteger(big.NewInt(int64(sabbaticalID))),
		stackitem.NewBigInteger(big.NewInt(int64(vitaID))),
		stackitem.NewBigInteger(big.NewInt(int64(purpose))),
	}))

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

// completeSabbatical completes a sabbatical.
func (s *Sese) completeSabbatical(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	sabbaticalID := toUint64(args[0])
	outcome := toString(args[1])

	sabbatical := s.getSabbaticalInternal(ic.DAO, sabbaticalID)
	if sabbatical == nil {
		panic(ErrSeseSabbaticalNotFound)
	}
	if sabbatical.Status != state.SabbaticalActive {
		panic(ErrSeseSabbaticalEnded)
	}

	// Check caller is owner or committee
	caller := ic.VM.GetCallingScriptHash()
	if caller != sabbatical.Owner && !s.checkCommittee(ic) {
		panic(ErrSeseNotOwner)
	}

	// Complete sabbatical
	sabbatical.EndedAt = ic.Block.Index
	sabbatical.Outcome = outcome
	sabbatical.Status = state.SabbaticalCompleted
	s.putSabbatical(ic.DAO, sabbatical)
	s.clearActiveSabbatical(ic.DAO, sabbatical.VitaID)

	// Emit event
	ic.AddNotification(s.Hash, SabbaticalCompletedEvent, stackitem.NewArray([]stackitem.Item{
		stackitem.NewBigInteger(big.NewInt(int64(sabbaticalID))),
	}))

	return stackitem.NewBool(true)
}

// cancelSabbatical cancels a sabbatical.
func (s *Sese) cancelSabbatical(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	sabbaticalID := toUint64(args[0])
	reason := toString(args[1])

	sabbatical := s.getSabbaticalInternal(ic.DAO, sabbaticalID)
	if sabbatical == nil {
		panic(ErrSeseSabbaticalNotFound)
	}
	if sabbatical.Status != state.SabbaticalActive && sabbatical.Status != state.SabbaticalPlanned {
		panic(ErrSeseSabbaticalEnded)
	}

	// Check caller is owner or committee
	caller := ic.VM.GetCallingScriptHash()
	if caller != sabbatical.Owner && !s.checkCommittee(ic) {
		panic(ErrSeseNotOwner)
	}

	// Cancel sabbatical
	sabbatical.EndedAt = ic.Block.Index
	sabbatical.Outcome = reason
	sabbatical.Status = state.SabbaticalCancelled
	s.putSabbatical(ic.DAO, sabbatical)
	s.clearActiveSabbatical(ic.DAO, sabbatical.VitaID)

	// Emit event
	ic.AddNotification(s.Hash, SabbaticalCancelledEvent, stackitem.NewArray([]stackitem.Item{
		stackitem.NewBigInteger(big.NewInt(int64(sabbaticalID))),
		stackitem.NewByteArray([]byte(reason)),
	}))

	return stackitem.NewBool(true)
}

// getSabbatical returns sabbatical details.
func (s *Sese) getSabbatical(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	sabbaticalID := toUint64(args[0])

	sabbatical := s.getSabbaticalInternal(ic.DAO, sabbaticalID)
	if sabbatical == nil {
		return stackitem.Null{}
	}

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

// getActiveSabbatical returns owner's active sabbatical.
func (s *Sese) getActiveSabbatical(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	owner := toUint160(args[0])

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

	sabbaticalID, hasActive := s.getActiveSabbaticalID(ic.DAO, vitaID)
	if !hasActive {
		return stackitem.Null{}
	}

	sabbatical := s.getSabbaticalInternal(ic.DAO, sabbaticalID)
	if sabbatical == nil {
		return stackitem.Null{}
	}

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

// recordMilestone records a life milestone.
func (s *Sese) recordMilestone(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	owner := toUint160(args[0])
	milestoneType := state.MilestoneType(toUint64(args[1]))
	title := toString(args[2])
	description := toString(args[3])
	contentHashBytes := toBytes(args[4])

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

	if len(title) == 0 || len(title) > 128 {
		panic(ErrSeseInvalidTitle)
	}
	if len(description) > 512 {
		panic(ErrSeseInvalidDescription)
	}

	// Get account
	vitaID, found := s.getVitaIDByOwner(ic.DAO, owner)
	if !found {
		panic(ErrSeseAccountNotFound)
	}

	acc := s.getAccountInternal(ic.DAO, vitaID)
	if acc == nil {
		panic(ErrSeseAccountNotFound)
	}
	if acc.Status != state.LifePlanAccountActive {
		panic(ErrSeseAccountSuspended)
	}

	// Get next milestone ID
	cache := ic.DAO.GetRWCache(s.ID).(*SeseCache)
	milestoneID := cache.milestoneCount
	cache.milestoneCount++
	s.setMilestoneCounter(ic.DAO, cache.milestoneCount)

	// Create milestone
	milestone := &state.LifeMilestone{
		ID:            milestoneID,
		VitaID:        vitaID,
		Owner:         owner,
		MilestoneType: milestoneType,
		Title:         title,
		Description:   description,
		ContentHash:   contentHash,
		AchievedAt:    ic.Block.Index,
		IsVerified:    false,
	}

	s.putMilestone(ic.DAO, milestone)

	// Emit event
	ic.AddNotification(s.Hash, MilestoneRecordedEvent, stackitem.NewArray([]stackitem.Item{
		stackitem.NewBigInteger(big.NewInt(int64(milestoneID))),
		stackitem.NewBigInteger(big.NewInt(int64(vitaID))),
		stackitem.NewBigInteger(big.NewInt(int64(milestoneType))),
	}))

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

// verifyMilestone verifies a milestone (life planner only).
func (s *Sese) verifyMilestone(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	milestoneID := toUint64(args[0])

	// Life planner only
	if !s.checkLifePlanner(ic) {
		panic(ErrSeseNotLifePlanner)
	}

	milestone := s.getMilestoneInternal(ic.DAO, milestoneID)
	if milestone == nil {
		panic(ErrSeseMilestoneNotFound)
	}

	milestone.IsVerified = true
	s.putMilestone(ic.DAO, milestone)

	// Emit event
	ic.AddNotification(s.Hash, MilestoneVerifiedEvent, stackitem.NewArray([]stackitem.Item{
		stackitem.NewBigInteger(big.NewInt(int64(milestoneID))),
	}))

	return stackitem.NewBool(true)
}

// getMilestone returns milestone details.
func (s *Sese) getMilestone(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	milestoneID := toUint64(args[0])

	milestone := s.getMilestoneInternal(ic.DAO, milestoneID)
	if milestone == nil {
		return stackitem.Null{}
	}

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

// createGoal creates a life goal.
func (s *Sese) createGoal(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	owner := toUint160(args[0])
	title := toString(args[1])
	description := toString(args[2])
	targetBlock := toUint32(args[3])

	if len(title) == 0 || len(title) > 128 {
		panic(ErrSeseInvalidTitle)
	}
	if len(description) > 512 {
		panic(ErrSeseInvalidDescription)
	}

	// Get account
	vitaID, found := s.getVitaIDByOwner(ic.DAO, owner)
	if !found {
		panic(ErrSeseAccountNotFound)
	}

	acc := s.getAccountInternal(ic.DAO, vitaID)
	if acc == nil {
		panic(ErrSeseAccountNotFound)
	}
	if acc.Status != state.LifePlanAccountActive {
		panic(ErrSeseAccountSuspended)
	}

	// Get next goal ID
	cache := ic.DAO.GetRWCache(s.ID).(*SeseCache)
	goalID := cache.goalCount
	cache.goalCount++
	s.setGoalCounter(ic.DAO, cache.goalCount)

	// Create goal
	goal := &state.LifeGoal{
		ID:          goalID,
		VitaID:      vitaID,
		Owner:       owner,
		Title:       title,
		Description: description,
		TargetBlock: targetBlock,
		Progress:    0,
		Status:      state.GoalStatusPlanned,
		CreatedAt:   ic.Block.Index,
		CompletedAt: 0,
	}

	s.putGoal(ic.DAO, goal)

	// Emit event
	ic.AddNotification(s.Hash, GoalCreatedEvent, stackitem.NewArray([]stackitem.Item{
		stackitem.NewBigInteger(big.NewInt(int64(goalID))),
		stackitem.NewBigInteger(big.NewInt(int64(vitaID))),
		stackitem.NewByteArray([]byte(title)),
	}))

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

// updateGoalProgress updates goal progress.
func (s *Sese) updateGoalProgress(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	goalID := toUint64(args[0])
	progress := toUint32(args[1])

	if progress > 10000 {
		panic(ErrSeseInvalidProgress)
	}

	goal := s.getGoalInternal(ic.DAO, goalID)
	if goal == nil {
		panic(ErrSeseGoalNotFound)
	}
	if goal.Status == state.GoalStatusCompleted || goal.Status == state.GoalStatusAbandoned {
		panic(ErrSeseGoalCompleted)
	}

	// Check caller is owner
	caller := ic.VM.GetCallingScriptHash()
	if caller != goal.Owner && !s.checkCommittee(ic) {
		panic(ErrSeseNotOwner)
	}

	goal.Progress = progress
	if goal.Status == state.GoalStatusPlanned {
		goal.Status = state.GoalStatusInProgress
	}
	s.putGoal(ic.DAO, goal)

	// Emit event
	ic.AddNotification(s.Hash, GoalUpdatedEvent, stackitem.NewArray([]stackitem.Item{
		stackitem.NewBigInteger(big.NewInt(int64(goalID))),
		stackitem.NewBigInteger(big.NewInt(int64(progress))),
	}))

	return stackitem.NewBool(true)
}

// completeGoal marks goal as completed.
func (s *Sese) completeGoal(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	goalID := toUint64(args[0])

	goal := s.getGoalInternal(ic.DAO, goalID)
	if goal == nil {
		panic(ErrSeseGoalNotFound)
	}
	if goal.Status == state.GoalStatusCompleted || goal.Status == state.GoalStatusAbandoned {
		panic(ErrSeseGoalCompleted)
	}

	// Check caller is owner
	caller := ic.VM.GetCallingScriptHash()
	if caller != goal.Owner && !s.checkCommittee(ic) {
		panic(ErrSeseNotOwner)
	}

	goal.Progress = 10000 // 100%
	goal.Status = state.GoalStatusCompleted
	goal.CompletedAt = ic.Block.Index
	s.putGoal(ic.DAO, goal)

	// Emit event
	ic.AddNotification(s.Hash, GoalCompletedEvent, stackitem.NewArray([]stackitem.Item{
		stackitem.NewBigInteger(big.NewInt(int64(goalID))),
	}))

	return stackitem.NewBool(true)
}

// abandonGoal abandons a goal.
func (s *Sese) abandonGoal(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	goalID := toUint64(args[0])
	// reason := toString(args[1]) // for logging

	goal := s.getGoalInternal(ic.DAO, goalID)
	if goal == nil {
		panic(ErrSeseGoalNotFound)
	}
	if goal.Status == state.GoalStatusCompleted || goal.Status == state.GoalStatusAbandoned {
		panic(ErrSeseGoalCompleted)
	}

	// Check caller is owner
	caller := ic.VM.GetCallingScriptHash()
	if caller != goal.Owner && !s.checkCommittee(ic) {
		panic(ErrSeseNotOwner)
	}

	goal.Status = state.GoalStatusAbandoned
	goal.CompletedAt = ic.Block.Index
	s.putGoal(ic.DAO, goal)

	return stackitem.NewBool(true)
}

// getGoal returns goal details.
func (s *Sese) getGoal(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	goalID := toUint64(args[0])

	goal := s.getGoalInternal(ic.DAO, goalID)
	if goal == nil {
		return stackitem.Null{}
	}

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

// getConfig returns the Sese configuration.
func (s *Sese) getConfig(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	cfg := s.getConfigInternal(ic.DAO)
	item, _ := cfg.ToStackItem()
	return item
}

// getTotalAccounts returns the total number of life plan accounts.
func (s *Sese) getTotalAccounts(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	cache := ic.DAO.GetROCache(s.ID).(*SeseCache)
	return stackitem.NewBigInteger(big.NewInt(int64(cache.accountCount)))
}

// getTotalCareers returns the total number of career cycles.
func (s *Sese) getTotalCareers(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	cache := ic.DAO.GetROCache(s.ID).(*SeseCache)
	return stackitem.NewBigInteger(big.NewInt(int64(cache.careerCount)))
}

// getTotalSabbaticals returns the total number of sabbaticals.
func (s *Sese) getTotalSabbaticals(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	cache := ic.DAO.GetROCache(s.ID).(*SeseCache)
	return stackitem.NewBigInteger(big.NewInt(int64(cache.sabbaticalCount)))
}

// getTotalMilestones returns the total number of milestones.
func (s *Sese) getTotalMilestones(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	cache := ic.DAO.GetROCache(s.ID).(*SeseCache)
	return stackitem.NewBigInteger(big.NewInt(int64(cache.milestoneCount)))
}

// getTotalGoals returns the total number of goals.
func (s *Sese) getTotalGoals(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	cache := ic.DAO.GetROCache(s.ID).(*SeseCache)
	return stackitem.NewBigInteger(big.NewInt(int64(cache.goalCount)))
}

// ===== Public Interface Methods for Cross-Contract Access =====

// GetAccountByOwner returns a life plan account by owner address.
func (s *Sese) GetAccountByOwner(d *dao.Simple, owner util.Uint160) (*state.LifePlanAccount, error) {
	vitaID, found := s.getVitaIDByOwner(d, owner)
	if !found {
		return nil, ErrSeseAccountNotFound
	}
	acc := s.getAccountInternal(d, vitaID)
	if acc == nil {
		return nil, ErrSeseAccountNotFound
	}
	return acc, nil
}

// HasActiveCareer checks if owner has an active career.
func (s *Sese) HasActiveCareer(d *dao.Simple, owner util.Uint160) bool {
	vitaID, found := s.getVitaIDByOwner(d, owner)
	if !found {
		return false
	}
	_, hasActive := s.getActiveCareerID(d, vitaID)
	return hasActive
}

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