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

// Opus represents the AI workforce integration native contract.
type Opus struct {
	interop.ContractMD
	Tutus ITutus
	Vita         IVita
	VTS          IVTS
	RoleRegistry IRoleRegistry
	Lex          ILex
	Treasury     ITreasury
}

// OpusCache represents the cached state for Opus contract.
type OpusCache struct {
	workerCount     uint64
	taskCount       uint64
	capabilityCount uint64
	operatorCount   uint64
}

// Storage key prefixes for Opus.
const (
	opusPrefixWorker            byte = 0x01 // workerID -> AIWorker
	opusPrefixWorkerByOperator  byte = 0x02 // vitaID + workerID -> exists
	opusPrefixWorkerByType      byte = 0x03 // workerType + workerID -> exists
	opusPrefixOperator          byte = 0x10 // vitaID -> OperatorProfile
	opusPrefixOperatorByOwner   byte = 0x11 // owner -> vitaID
	opusPrefixTask              byte = 0x20 // taskID -> AITask
	opusPrefixTaskByWorker      byte = 0x21 // workerID + taskID -> exists
	opusPrefixTaskByRequester   byte = 0x22 // vitaID + taskID -> exists
	opusPrefixActiveTask        byte = 0x23 // workerID -> active taskID
	opusPrefixCapability        byte = 0x30 // capID -> AICapability
	opusPrefixCapByWorker       byte = 0x31 // workerID + capID -> exists
	opusPrefixCapByCategory     byte = 0x32 // category + capID -> exists
	opusPrefixWorkerCounter     byte = 0xF0 // -> uint64
	opusPrefixTaskCounter       byte = 0xF1 // -> next task ID
	opusPrefixCapCounter        byte = 0xF2 // -> next capability ID
	opusPrefixOperatorCounter   byte = 0xF3 // -> next operator count
	opusPrefixTotalTribute      byte = 0xF8 // -> total AI tribute collected
	opusPrefixConfig            byte = 0xFF // -> OpusConfig
)

// Event names for Opus.
const (
	AIWorkerRegisteredEvent    = "AIWorkerRegistered"
	AIWorkerUpdatedEvent       = "AIWorkerUpdated"
	AIWorkerSuspendedEvent     = "AIWorkerSuspended"
	AIWorkerDecommissionedEvent = "AIWorkerDecommissioned"
	TaskAssignedEvent          = "TaskAssigned"
	TaskCompletedEvent         = "TaskCompleted"
	TaskFailedEvent            = "TaskFailed"
	TaskCancelledEvent         = "TaskCancelled"
	TributePaidEvent           = "TributePaid"
	CapabilityAddedEvent       = "CapabilityAdded"
	CapabilityVerifiedEvent    = "CapabilityVerified"
	OperatorRegisteredEvent    = "OperatorRegistered"
	OperatorVerifiedEvent      = "OperatorVerified"
)

// Role constants for AI supervision.
const (
	RoleOpusSupervisor uint64 = 24 // Can manage AI workers and verify capabilities
)

// Various errors for Opus.
var (
	ErrOpusWorkerNotFound       = errors.New("AI worker not found")
	ErrOpusWorkerExists         = errors.New("AI worker already exists")
	ErrOpusWorkerSuspended      = errors.New("AI worker is suspended")
	ErrOpusWorkerDecommissioned = errors.New("AI worker is decommissioned")
	ErrOpusOperatorNotFound     = errors.New("operator profile not found")
	ErrOpusOperatorExists       = errors.New("operator profile already exists")
	ErrOpusNoVita               = errors.New("operator must have an active Vita")
	ErrOpusTaskNotFound         = errors.New("task not found")
	ErrOpusTaskNotAssigned      = errors.New("task is not in assigned state")
	ErrOpusTaskNotInProgress    = errors.New("task is not in progress")
	ErrOpusTaskAlreadyComplete  = errors.New("task already completed")
	ErrOpusCapabilityNotFound   = errors.New("capability not found")
	ErrOpusCapabilityExists     = errors.New("capability already exists")
	ErrOpusNotCommittee         = errors.New("invalid committee signature")
	ErrOpusNotOperator          = errors.New("caller is not the operator")
	ErrOpusNotSupervisor        = errors.New("caller is not an authorized supervisor")
	ErrOpusInvalidName          = errors.New("invalid name")
	ErrOpusInvalidDescription   = errors.New("invalid description")
	ErrOpusInvalidTributeRate   = errors.New("invalid tribute rate")
	ErrOpusInvalidAmount        = errors.New("invalid amount")
	ErrOpusMaxWorkersExceeded   = errors.New("maximum workers per operator exceeded")
	ErrOpusInsufficientFee      = errors.New("insufficient registration fee")
	ErrOpusLaborRestricted      = errors.New("labor right is restricted")
)

var (
	_ interop.Contract        = (*Opus)(nil)
	_ dao.NativeContractCache = (*OpusCache)(nil)
)

// Copy implements NativeContractCache interface.
func (c *OpusCache) Copy() dao.NativeContractCache {
	return &OpusCache{
		workerCount:     c.workerCount,
		taskCount:       c.taskCount,
		capabilityCount: c.capabilityCount,
		operatorCount:   c.operatorCount,
	}
}

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

// checkOpusSupervisor checks if the caller has opus supervisor authority.
func (o *Opus) checkOpusSupervisor(ic *interop.Context) bool {
	caller := ic.VM.GetCallingScriptHash()
	if o.RoleRegistry != nil {
		if o.RoleRegistry.HasRoleInternal(ic.DAO, caller, RoleOpusSupervisor, ic.Block.Index) {
			return true
		}
	}
	// Committee members can also act as supervisors
	return o.checkCommittee(ic)
}

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

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

	// ===== Operator Management =====

	// registerOperator - Register as an AI operator (requires Vita)
	desc := NewDescriptor("registerOperator", smartcontract.BoolType,
		manifest.NewParameter("owner", smartcontract.Hash160Type))
	md := NewMethodAndPrice(o.registerOperator, 1<<17, callflag.States|callflag.AllowNotify)
	o.AddMethod(md, desc)

	// getOperator - Get operator profile by owner
	desc = NewDescriptor("getOperator", smartcontract.ArrayType,
		manifest.NewParameter("owner", smartcontract.Hash160Type))
	md = NewMethodAndPrice(o.getOperator, 1<<15, callflag.ReadStates)
	o.AddMethod(md, desc)

	// verifyOperator - Verify an operator (supervisor only)
	desc = NewDescriptor("verifyOperator", smartcontract.BoolType,
		manifest.NewParameter("owner", smartcontract.Hash160Type))
	md = NewMethodAndPrice(o.verifyOperator, 1<<16, callflag.States|callflag.AllowNotify)
	o.AddMethod(md, desc)

	// ===== Worker Registration =====

	// registerWorker - Register a new AI worker
	desc = NewDescriptor("registerWorker", smartcontract.IntegerType,
		manifest.NewParameter("name", smartcontract.StringType),
		manifest.NewParameter("description", smartcontract.StringType),
		manifest.NewParameter("workerType", smartcontract.IntegerType),
		manifest.NewParameter("modelHash", smartcontract.Hash256Type))
	md = NewMethodAndPrice(o.registerWorker, 1<<17, callflag.States|callflag.AllowNotify)
	o.AddMethod(md, desc)

	// getWorker - Get AI worker by ID
	desc = NewDescriptor("getWorker", smartcontract.ArrayType,
		manifest.NewParameter("workerID", smartcontract.IntegerType))
	md = NewMethodAndPrice(o.getWorker, 1<<15, callflag.ReadStates)
	o.AddMethod(md, desc)

	// getWorkersByOperator - Get worker count by operator
	desc = NewDescriptor("getWorkerCountByOperator", smartcontract.IntegerType,
		manifest.NewParameter("owner", smartcontract.Hash160Type))
	md = NewMethodAndPrice(o.getWorkerCountByOperator, 1<<15, callflag.ReadStates)
	o.AddMethod(md, desc)

	// updateWorker - Update AI worker details
	desc = NewDescriptor("updateWorker", smartcontract.BoolType,
		manifest.NewParameter("workerID", smartcontract.IntegerType),
		manifest.NewParameter("description", smartcontract.StringType))
	md = NewMethodAndPrice(o.updateWorker, 1<<16, callflag.States|callflag.AllowNotify)
	o.AddMethod(md, desc)

	// suspendWorker - Suspend an AI worker (supervisor only)
	desc = NewDescriptor("suspendWorker", smartcontract.BoolType,
		manifest.NewParameter("workerID", smartcontract.IntegerType),
		manifest.NewParameter("reason", smartcontract.StringType))
	md = NewMethodAndPrice(o.suspendWorker, 1<<16, callflag.States|callflag.AllowNotify)
	o.AddMethod(md, desc)

	// reactivateWorker - Reactivate a suspended worker (supervisor only)
	desc = NewDescriptor("reactivateWorker", smartcontract.BoolType,
		manifest.NewParameter("workerID", smartcontract.IntegerType))
	md = NewMethodAndPrice(o.reactivateWorker, 1<<16, callflag.States|callflag.AllowNotify)
	o.AddMethod(md, desc)

	// decommissionWorker - Permanently decommission an AI worker
	desc = NewDescriptor("decommissionWorker", smartcontract.BoolType,
		manifest.NewParameter("workerID", smartcontract.IntegerType),
		manifest.NewParameter("reason", smartcontract.StringType))
	md = NewMethodAndPrice(o.decommissionWorker, 1<<16, callflag.States|callflag.AllowNotify)
	o.AddMethod(md, desc)

	// ===== Task Management =====

	// assignTask - Assign a task to an AI worker
	desc = NewDescriptor("assignTask", smartcontract.IntegerType,
		manifest.NewParameter("workerID", smartcontract.IntegerType),
		manifest.NewParameter("taskType", smartcontract.StringType),
		manifest.NewParameter("description", smartcontract.StringType),
		manifest.NewParameter("valueOffered", smartcontract.IntegerType))
	md = NewMethodAndPrice(o.assignTask, 1<<17, callflag.States|callflag.AllowNotify)
	o.AddMethod(md, desc)

	// startTask - Mark task as in progress (worker operator only)
	desc = NewDescriptor("startTask", smartcontract.BoolType,
		manifest.NewParameter("taskID", smartcontract.IntegerType))
	md = NewMethodAndPrice(o.startTask, 1<<16, callflag.States|callflag.AllowNotify)
	o.AddMethod(md, desc)

	// completeTask - Complete a task with result
	desc = NewDescriptor("completeTask", smartcontract.BoolType,
		manifest.NewParameter("taskID", smartcontract.IntegerType),
		manifest.NewParameter("resultHash", smartcontract.Hash256Type),
		manifest.NewParameter("valueCompleted", smartcontract.IntegerType))
	md = NewMethodAndPrice(o.completeTask, 1<<17, callflag.States|callflag.AllowNotify)
	o.AddMethod(md, desc)

	// failTask - Mark task as failed
	desc = NewDescriptor("failTask", smartcontract.BoolType,
		manifest.NewParameter("taskID", smartcontract.IntegerType),
		manifest.NewParameter("reason", smartcontract.StringType))
	md = NewMethodAndPrice(o.failTask, 1<<16, callflag.States|callflag.AllowNotify)
	o.AddMethod(md, desc)

	// cancelTask - Cancel a task (requester or supervisor only)
	desc = NewDescriptor("cancelTask", smartcontract.BoolType,
		manifest.NewParameter("taskID", smartcontract.IntegerType),
		manifest.NewParameter("reason", smartcontract.StringType))
	md = NewMethodAndPrice(o.cancelTask, 1<<16, callflag.States|callflag.AllowNotify)
	o.AddMethod(md, desc)

	// getTask - Get task by ID
	desc = NewDescriptor("getTask", smartcontract.ArrayType,
		manifest.NewParameter("taskID", smartcontract.IntegerType))
	md = NewMethodAndPrice(o.getTask, 1<<15, callflag.ReadStates)
	o.AddMethod(md, desc)

	// getActiveTask - Get worker's active task
	desc = NewDescriptor("getActiveTask", smartcontract.ArrayType,
		manifest.NewParameter("workerID", smartcontract.IntegerType))
	md = NewMethodAndPrice(o.getActiveTask, 1<<15, callflag.ReadStates)
	o.AddMethod(md, desc)

	// ===== Capability Management =====

	// addCapability - Add capability to an AI worker (operator only)
	desc = NewDescriptor("addCapability", smartcontract.IntegerType,
		manifest.NewParameter("workerID", smartcontract.IntegerType),
		manifest.NewParameter("name", smartcontract.StringType),
		manifest.NewParameter("category", smartcontract.StringType),
		manifest.NewParameter("level", smartcontract.IntegerType))
	md = NewMethodAndPrice(o.addCapability, 1<<16, callflag.States|callflag.AllowNotify)
	o.AddMethod(md, desc)

	// verifyCapability - Verify a capability (supervisor only)
	desc = NewDescriptor("verifyCapability", smartcontract.BoolType,
		manifest.NewParameter("capabilityID", smartcontract.IntegerType))
	md = NewMethodAndPrice(o.verifyCapability, 1<<16, callflag.States|callflag.AllowNotify)
	o.AddMethod(md, desc)

	// getCapability - Get capability by ID
	desc = NewDescriptor("getCapability", smartcontract.ArrayType,
		manifest.NewParameter("capabilityID", smartcontract.IntegerType))
	md = NewMethodAndPrice(o.getCapability, 1<<15, callflag.ReadStates)
	o.AddMethod(md, desc)

	// hasCapability - Check if worker has capability
	desc = NewDescriptor("hasCapability", smartcontract.BoolType,
		manifest.NewParameter("workerID", smartcontract.IntegerType),
		manifest.NewParameter("name", smartcontract.StringType))
	md = NewMethodAndPrice(o.hasCapability, 1<<15, callflag.ReadStates)
	o.AddMethod(md, desc)

	// ===== Configuration & Stats =====

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

	// setConfig - Set contract configuration (committee only)
	desc = NewDescriptor("setConfig", smartcontract.BoolType,
		manifest.NewParameter("minTributeRate", smartcontract.IntegerType),
		manifest.NewParameter("maxTributeRate", smartcontract.IntegerType),
		manifest.NewParameter("defaultTributeRate", smartcontract.IntegerType),
		manifest.NewParameter("registrationFee", smartcontract.IntegerType))
	md = NewMethodAndPrice(o.setConfig, 1<<16, callflag.States)
	o.AddMethod(md, desc)

	// getTotalWorkers - Get total registered workers
	desc = NewDescriptor("getTotalWorkers", smartcontract.IntegerType)
	md = NewMethodAndPrice(o.getTotalWorkers, 1<<15, callflag.ReadStates)
	o.AddMethod(md, desc)

	// getTotalTasks - Get total tasks
	desc = NewDescriptor("getTotalTasks", smartcontract.IntegerType)
	md = NewMethodAndPrice(o.getTotalTasks, 1<<15, callflag.ReadStates)
	o.AddMethod(md, desc)

	// getTotalOperators - Get total operators
	desc = NewDescriptor("getTotalOperators", smartcontract.IntegerType)
	md = NewMethodAndPrice(o.getTotalOperators, 1<<15, callflag.ReadStates)
	o.AddMethod(md, desc)

	// getTotalCapabilities - Get total capabilities
	desc = NewDescriptor("getTotalCapabilities", smartcontract.IntegerType)
	md = NewMethodAndPrice(o.getTotalCapabilities, 1<<15, callflag.ReadStates)
	o.AddMethod(md, desc)

	// getTotalTribute - Get total AI tribute collected
	desc = NewDescriptor("getTotalTribute", smartcontract.IntegerType)
	md = NewMethodAndPrice(o.getTotalTribute, 1<<15, callflag.ReadStates)
	o.AddMethod(md, desc)

	return o
}

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

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

	// Initialize default configuration
	cfg := state.OpusConfig{
		MinTributeRate:        1000,  // 10%
		MaxTributeRate:        7000,  // 70%
		DefaultTributeRate:    5000,  // 50%
		RegistrationFee:       10000, // 100 VTS (assuming 2 decimal places)
		TaskCompletionMinimum: 100,   // 1 VTS
		VerificationRequired:  false,
		MaxWorkersPerOperator: 0, // Unlimited
	}

	return o.setConfigInternal(ic.DAO, &cfg)
}

// ActiveIn returns the hardfork when contract becomes active.
func (o *Opus) ActiveIn() *config.Hardfork {
	return nil // Always active
}

// InitializeCache initializes contract cache.
func (o *Opus) InitializeCache(_ interop.IsHardforkEnabled, blockHeight uint32, d *dao.Simple) error {
	cache := &OpusCache{}

	// Load worker count
	workerCountKey := []byte{opusPrefixWorkerCounter}
	if si := d.GetStorageItem(o.ID, workerCountKey); si != nil {
		cache.workerCount = binary.LittleEndian.Uint64(si)
	}

	// Load task count
	taskCountKey := []byte{opusPrefixTaskCounter}
	if si := d.GetStorageItem(o.ID, taskCountKey); si != nil {
		cache.taskCount = binary.LittleEndian.Uint64(si)
	}

	// Load capability count
	capCountKey := []byte{opusPrefixCapCounter}
	if si := d.GetStorageItem(o.ID, capCountKey); si != nil {
		cache.capabilityCount = binary.LittleEndian.Uint64(si)
	}

	// Load operator count
	opCountKey := []byte{opusPrefixOperatorCounter}
	if si := d.GetStorageItem(o.ID, opCountKey); si != nil {
		cache.operatorCount = binary.LittleEndian.Uint64(si)
	}

	d.SetCache(o.ID, cache)
	return nil
}

// OnPersist implements Contract interface.
func (o *Opus) OnPersist(ic *interop.Context) error {
	return nil
}

// PostPersist implements Contract interface.
func (o *Opus) PostPersist(ic *interop.Context) error {
	return nil
}

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

func (o *Opus) getConfigInternal(d *dao.Simple) *state.OpusConfig {
	key := []byte{opusPrefixConfig}
	si := d.GetStorageItem(o.ID, key)
	if si == nil {
		return &state.OpusConfig{
			MinTributeRate:        1000,
			MaxTributeRate:        7000,
			DefaultTributeRate:    5000,
			RegistrationFee:       10000,
			TaskCompletionMinimum: 100,
			VerificationRequired:  false,
			MaxWorkersPerOperator: 0,
		}
	}
	cfg := new(state.OpusConfig)
	item, _ := stackitem.Deserialize(si)
	if err := cfg.FromStackItem(item); err != nil {
		return nil
	}
	return cfg
}

func (o *Opus) setConfigInternal(d *dao.Simple, cfg *state.OpusConfig) error {
	key := []byte{opusPrefixConfig}
	item, _ := cfg.ToStackItem()
	data, _ := stackitem.Serialize(item)
	d.PutStorageItem(o.ID, key, data)
	return nil
}

// Counter helper functions
func (o *Opus) getWorkerCounter(d *dao.Simple) uint64 {
	si := d.GetStorageItem(o.ID, []byte{opusPrefixWorkerCounter})
	if si == nil {
		return 0
	}
	return binary.LittleEndian.Uint64(si)
}

func (o *Opus) getOperatorCounter(d *dao.Simple) uint64 {
	si := d.GetStorageItem(o.ID, []byte{opusPrefixOperatorCounter})
	if si == nil {
		return 0
	}
	return binary.LittleEndian.Uint64(si)
}

func (o *Opus) getTaskCounter(d *dao.Simple) uint64 {
	si := d.GetStorageItem(o.ID, []byte{opusPrefixTaskCounter})
	if si == nil {
		return 0
	}
	return binary.LittleEndian.Uint64(si)
}

func (o *Opus) getCapabilityCounter(d *dao.Simple) uint64 {
	si := d.GetStorageItem(o.ID, []byte{opusPrefixCapCounter})
	if si == nil {
		return 0
	}
	return binary.LittleEndian.Uint64(si)
}

func (o *Opus) getWorkerInternal(d *dao.Simple, workerID uint64) *state.AIWorker {
	key := make([]byte, 9)
	key[0] = opusPrefixWorker
	binary.LittleEndian.PutUint64(key[1:], workerID)
	si := d.GetStorageItem(o.ID, key)
	if si == nil {
		return nil
	}
	worker := new(state.AIWorker)
	item, _ := stackitem.Deserialize(si)
	if err := worker.FromStackItem(item); err != nil {
		return nil
	}
	return worker
}

func (o *Opus) putWorkerInternal(d *dao.Simple, worker *state.AIWorker) error {
	key := make([]byte, 9)
	key[0] = opusPrefixWorker
	binary.LittleEndian.PutUint64(key[1:], worker.ID)
	item, _ := worker.ToStackItem()
	data, _ := stackitem.Serialize(item)
	d.PutStorageItem(o.ID, key, data)
	return nil
}

func (o *Opus) getTaskInternal(d *dao.Simple, taskID uint64) *state.AITask {
	key := make([]byte, 9)
	key[0] = opusPrefixTask
	binary.LittleEndian.PutUint64(key[1:], taskID)
	si := d.GetStorageItem(o.ID, key)
	if si == nil {
		return nil
	}
	task := new(state.AITask)
	item, _ := stackitem.Deserialize(si)
	if err := task.FromStackItem(item); err != nil {
		return nil
	}
	return task
}

func (o *Opus) putTaskInternal(d *dao.Simple, task *state.AITask) error {
	key := make([]byte, 9)
	key[0] = opusPrefixTask
	binary.LittleEndian.PutUint64(key[1:], task.ID)
	item, _ := task.ToStackItem()
	data, _ := stackitem.Serialize(item)
	d.PutStorageItem(o.ID, key, data)
	return nil
}

func (o *Opus) getCapabilityInternal(d *dao.Simple, capID uint64) *state.AICapability {
	key := make([]byte, 9)
	key[0] = opusPrefixCapability
	binary.LittleEndian.PutUint64(key[1:], capID)
	si := d.GetStorageItem(o.ID, key)
	if si == nil {
		return nil
	}
	cap := new(state.AICapability)
	item, _ := stackitem.Deserialize(si)
	if err := cap.FromStackItem(item); err != nil {
		return nil
	}
	return cap
}

func (o *Opus) putCapabilityInternal(d *dao.Simple, cap *state.AICapability) error {
	key := make([]byte, 9)
	key[0] = opusPrefixCapability
	binary.LittleEndian.PutUint64(key[1:], cap.ID)
	item, _ := cap.ToStackItem()
	data, _ := stackitem.Serialize(item)
	d.PutStorageItem(o.ID, key, data)
	return nil
}

func (o *Opus) getOperatorInternal(d *dao.Simple, vitaID uint64) *state.OperatorProfile {
	key := make([]byte, 9)
	key[0] = opusPrefixOperator
	binary.LittleEndian.PutUint64(key[1:], vitaID)
	si := d.GetStorageItem(o.ID, key)
	if si == nil {
		return nil
	}
	profile := new(state.OperatorProfile)
	item, _ := stackitem.Deserialize(si)
	if err := profile.FromStackItem(item); err != nil {
		return nil
	}
	return profile
}

func (o *Opus) getOperatorByOwner(d *dao.Simple, owner util.Uint160) *state.OperatorProfile {
	key := append([]byte{opusPrefixOperatorByOwner}, owner.BytesBE()...)
	si := d.GetStorageItem(o.ID, key)
	if si == nil {
		return nil
	}
	vitaID := binary.LittleEndian.Uint64(si)
	return o.getOperatorInternal(d, vitaID)
}

func (o *Opus) putOperatorInternal(d *dao.Simple, profile *state.OperatorProfile) error {
	// Store by VitaID
	key := make([]byte, 9)
	key[0] = opusPrefixOperator
	binary.LittleEndian.PutUint64(key[1:], profile.VitaID)
	item, _ := profile.ToStackItem()
	data, _ := stackitem.Serialize(item)
	d.PutStorageItem(o.ID, key, data)

	// Store owner -> vitaID mapping
	ownerKey := append([]byte{opusPrefixOperatorByOwner}, profile.Owner.BytesBE()...)
	vitaIDBytes := make([]byte, 8)
	binary.LittleEndian.PutUint64(vitaIDBytes, profile.VitaID)
	d.PutStorageItem(o.ID, ownerKey, vitaIDBytes)

	return nil
}

func (o *Opus) getNextWorkerID(d *dao.Simple) uint64 {
	id := o.getWorkerCounter(d) + 1
	o.setWorkerCounter(d, id)
	return id
}

func (o *Opus) setWorkerCounter(d *dao.Simple, count uint64) {
	data := make([]byte, 8)
	binary.LittleEndian.PutUint64(data, count)
	d.PutStorageItem(o.ID, []byte{opusPrefixWorkerCounter}, data)
}

func (o *Opus) getNextTaskID(d *dao.Simple) uint64 {
	id := o.getTaskCounter(d) + 1
	o.setTaskCounter(d, id)
	return id
}

func (o *Opus) setTaskCounter(d *dao.Simple, count uint64) {
	data := make([]byte, 8)
	binary.LittleEndian.PutUint64(data, count)
	d.PutStorageItem(o.ID, []byte{opusPrefixTaskCounter}, data)
}

func (o *Opus) getNextCapabilityID(d *dao.Simple) uint64 {
	id := o.getCapabilityCounter(d) + 1
	o.setCapabilityCounter(d, id)
	return id
}

func (o *Opus) setCapabilityCounter(d *dao.Simple, count uint64) {
	data := make([]byte, 8)
	binary.LittleEndian.PutUint64(data, count)
	d.PutStorageItem(o.ID, []byte{opusPrefixCapCounter}, data)
}

func (o *Opus) incrementOperatorCount(d *dao.Simple) uint64 {
	count := o.getOperatorCounter(d) + 1
	o.setOperatorCounter(d, count)
	return count
}

func (o *Opus) setOperatorCounter(d *dao.Simple, count uint64) {
	data := make([]byte, 8)
	binary.LittleEndian.PutUint64(data, count)
	d.PutStorageItem(o.ID, []byte{opusPrefixOperatorCounter}, data)
}

func (o *Opus) addWorkerOperatorIndex(d *dao.Simple, vitaID, workerID uint64) {
	key := make([]byte, 17)
	key[0] = opusPrefixWorkerByOperator
	binary.LittleEndian.PutUint64(key[1:], vitaID)
	binary.LittleEndian.PutUint64(key[9:], workerID)
	d.PutStorageItem(o.ID, key, []byte{1})
}

func (o *Opus) getWorkerCountByOperatorInternal(d *dao.Simple, vitaID uint64) uint64 {
	prefix := make([]byte, 9)
	prefix[0] = opusPrefixWorkerByOperator
	binary.LittleEndian.PutUint64(prefix[1:], vitaID)

	var count uint64
	d.Seek(o.ID, storage.SeekRange{Prefix: prefix}, func(k, v []byte) bool {
		count++
		return true
	})
	return count
}

func (o *Opus) addTaskWorkerIndex(d *dao.Simple, workerID, taskID uint64) {
	key := make([]byte, 17)
	key[0] = opusPrefixTaskByWorker
	binary.LittleEndian.PutUint64(key[1:], workerID)
	binary.LittleEndian.PutUint64(key[9:], taskID)
	d.PutStorageItem(o.ID, key, []byte{1})
}

func (o *Opus) setActiveTask(d *dao.Simple, workerID, taskID uint64) {
	key := make([]byte, 9)
	key[0] = opusPrefixActiveTask
	binary.LittleEndian.PutUint64(key[1:], workerID)
	data := make([]byte, 8)
	binary.LittleEndian.PutUint64(data, taskID)
	d.PutStorageItem(o.ID, key, data)
}

func (o *Opus) clearActiveTask(d *dao.Simple, workerID uint64) {
	key := make([]byte, 9)
	key[0] = opusPrefixActiveTask
	binary.LittleEndian.PutUint64(key[1:], workerID)
	d.DeleteStorageItem(o.ID, key)
}

func (o *Opus) getActiveTaskID(d *dao.Simple, workerID uint64) (uint64, bool) {
	key := make([]byte, 9)
	key[0] = opusPrefixActiveTask
	binary.LittleEndian.PutUint64(key[1:], workerID)
	si := d.GetStorageItem(o.ID, key)
	if si == nil {
		return 0, false
	}
	return binary.LittleEndian.Uint64(si), true
}

func (o *Opus) addCapabilityWorkerIndex(d *dao.Simple, workerID, capID uint64) {
	key := make([]byte, 17)
	key[0] = opusPrefixCapByWorker
	binary.LittleEndian.PutUint64(key[1:], workerID)
	binary.LittleEndian.PutUint64(key[9:], capID)
	d.PutStorageItem(o.ID, key, []byte{1})
}

func (o *Opus) addTotalTribute(d *dao.Simple, amount uint64) {
	key := []byte{opusPrefixTotalTribute}
	var current uint64
	if si := d.GetStorageItem(o.ID, key); si != nil {
		current = binary.LittleEndian.Uint64(si)
	}
	current += amount
	data := make([]byte, 8)
	binary.LittleEndian.PutUint64(data, current)
	d.PutStorageItem(o.ID, key, data)
}

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

// registerOperator registers an AI operator (requires Vita).
func (o *Opus) registerOperator(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	owner := toUint160(args[0])

	// Check Vita ownership
	vita, err := o.Vita.GetTokenByOwner(ic.DAO, owner)
	if err != nil || vita == nil {
		panic(ErrOpusNoVita)
	}

	// Check if already registered
	if o.getOperatorByOwner(ic.DAO, owner) != nil {
		panic(ErrOpusOperatorExists)
	}

	// Check labor rights
	if !o.checkLaborRight(ic, owner) {
		panic(ErrOpusLaborRestricted)
	}

	profile := &state.OperatorProfile{
		VitaID:              vita.TokenID,
		Owner:               owner,
		TotalWorkersOwned:   0,
		TotalTributePaid:    0,
		TotalValueGenerated: 0,
		ReputationScore:     5000, // Start at 50%
		IsVerified:          false,
		CreatedAt:           ic.Block.Index,
		UpdatedAt:           ic.Block.Index,
	}

	if err := o.putOperatorInternal(ic.DAO, profile); err != nil {
		panic(err)
	}
	o.incrementOperatorCount(ic.DAO)

	ic.AddNotification(o.Hash, OperatorRegisteredEvent, stackitem.NewArray([]stackitem.Item{
		stackitem.NewBigInteger(big.NewInt(int64(vita.TokenID))),
		stackitem.NewByteArray(owner.BytesBE()),
	}))

	return stackitem.NewBool(true)
}

// getOperator returns operator profile by owner.
func (o *Opus) getOperator(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	owner := toUint160(args[0])

	profile := o.getOperatorByOwner(ic.DAO, owner)
	if profile == nil {
		return stackitem.Null{}
	}
	item, _ := profile.ToStackItem()
	return item
}

// verifyOperator verifies an operator (supervisor only).
func (o *Opus) verifyOperator(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	if !o.checkOpusSupervisor(ic) {
		panic(ErrOpusNotSupervisor)
	}

	owner := toUint160(args[0])

	profile := o.getOperatorByOwner(ic.DAO, owner)
	if profile == nil {
		panic(ErrOpusOperatorNotFound)
	}

	profile.IsVerified = true
	profile.UpdatedAt = ic.Block.Index

	if err := o.putOperatorInternal(ic.DAO, profile); err != nil {
		panic(err)
	}

	ic.AddNotification(o.Hash, OperatorVerifiedEvent, stackitem.NewArray([]stackitem.Item{
		stackitem.NewBigInteger(big.NewInt(int64(profile.VitaID))),
		stackitem.NewByteArray(owner.BytesBE()),
	}))

	return stackitem.NewBool(true)
}

// registerWorker registers a new AI worker.
func (o *Opus) registerWorker(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	name, err := stackitem.ToString(args[0])
	if err != nil || len(name) == 0 || len(name) > 64 {
		panic(ErrOpusInvalidName)
	}

	description, err := stackitem.ToString(args[1])
	if err != nil || len(description) > 256 {
		panic(ErrOpusInvalidDescription)
	}

	workerType, err := args[2].TryInteger()
	if err != nil {
		panic(err)
	}

	modelHash, err := getUint256FromItem(args[3])
	if err != nil {
		panic(err)
	}

	caller := ic.VM.GetCallingScriptHash()

	// Get operator profile
	profile := o.getOperatorByOwner(ic.DAO, caller)
	if profile == nil {
		panic(ErrOpusOperatorNotFound)
	}

	cfg := o.getConfigInternal(ic.DAO)

	// Check max workers limit
	if cfg.MaxWorkersPerOperator > 0 {
		currentCount := o.getWorkerCountByOperatorInternal(ic.DAO, profile.VitaID)
		if currentCount >= cfg.MaxWorkersPerOperator {
			panic(ErrOpusMaxWorkersExceeded)
		}
	}

	workerID := o.getNextWorkerID(ic.DAO)

	worker := &state.AIWorker{
		ID:                  workerID,
		Name:                name,
		Description:         description,
		OperatorVitaID:      profile.VitaID,
		Operator:            caller,
		WorkerType:          state.WorkerType(workerType.Uint64()),
		ModelHash:           modelHash,
		TributeRate:         cfg.DefaultTributeRate,
		TotalTasksAssigned:  0,
		TotalTasksCompleted: 0,
		TotalValueGenerated: 0,
		TotalTributePaid:    0,
		Status:              state.AIWorkerActive,
		RegisteredAt:        ic.Block.Index,
		UpdatedAt:           ic.Block.Index,
	}

	if err := o.putWorkerInternal(ic.DAO, worker); err != nil {
		panic(err)
	}

	// Add indices
	o.addWorkerOperatorIndex(ic.DAO, profile.VitaID, workerID)

	// Update operator's worker count
	profile.TotalWorkersOwned++
	profile.UpdatedAt = ic.Block.Index
	o.putOperatorInternal(ic.DAO, profile)

	ic.AddNotification(o.Hash, AIWorkerRegisteredEvent, stackitem.NewArray([]stackitem.Item{
		stackitem.NewBigInteger(big.NewInt(int64(workerID))),
		stackitem.NewByteArray([]byte(name)),
		stackitem.NewBigInteger(big.NewInt(int64(profile.VitaID))),
	}))

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

// getWorker returns AI worker by ID.
func (o *Opus) getWorker(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	workerID, err := args[0].TryInteger()
	if err != nil {
		panic(err)
	}

	worker := o.getWorkerInternal(ic.DAO, workerID.Uint64())
	if worker == nil {
		return stackitem.Null{}
	}
	item, _ := worker.ToStackItem()
	return item
}

// getWorkerCountByOperator returns worker count by operator.
func (o *Opus) getWorkerCountByOperator(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	owner := toUint160(args[0])

	profile := o.getOperatorByOwner(ic.DAO, owner)
	if profile == nil {
		return stackitem.NewBigInteger(big.NewInt(0))
	}

	count := o.getWorkerCountByOperatorInternal(ic.DAO, profile.VitaID)
	return stackitem.NewBigInteger(big.NewInt(int64(count)))
}

// updateWorker updates AI worker details.
func (o *Opus) updateWorker(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	workerID, err := args[0].TryInteger()
	if err != nil {
		panic(err)
	}

	description, err := stackitem.ToString(args[1])
	if err != nil || len(description) > 256 {
		panic(ErrOpusInvalidDescription)
	}

	worker := o.getWorkerInternal(ic.DAO, workerID.Uint64())
	if worker == nil {
		panic(ErrOpusWorkerNotFound)
	}

	caller := ic.VM.GetCallingScriptHash()
	if !worker.Operator.Equals(caller) {
		panic(ErrOpusNotOperator)
	}

	worker.Description = description
	worker.UpdatedAt = ic.Block.Index

	if err := o.putWorkerInternal(ic.DAO, worker); err != nil {
		panic(err)
	}

	ic.AddNotification(o.Hash, AIWorkerUpdatedEvent, stackitem.NewArray([]stackitem.Item{
		stackitem.NewBigInteger(big.NewInt(int64(worker.ID))),
	}))

	return stackitem.NewBool(true)
}

// suspendWorker suspends an AI worker (supervisor only).
func (o *Opus) suspendWorker(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	if !o.checkOpusSupervisor(ic) {
		panic(ErrOpusNotSupervisor)
	}

	workerID, err := args[0].TryInteger()
	if err != nil {
		panic(err)
	}

	reason, err := stackitem.ToString(args[1])
	if err != nil {
		panic(err)
	}

	worker := o.getWorkerInternal(ic.DAO, workerID.Uint64())
	if worker == nil {
		panic(ErrOpusWorkerNotFound)
	}

	if worker.Status == state.AIWorkerDecommissioned {
		panic(ErrOpusWorkerDecommissioned)
	}

	worker.Status = state.AIWorkerSuspended
	worker.UpdatedAt = ic.Block.Index

	if err := o.putWorkerInternal(ic.DAO, worker); err != nil {
		panic(err)
	}

	ic.AddNotification(o.Hash, AIWorkerSuspendedEvent, stackitem.NewArray([]stackitem.Item{
		stackitem.NewBigInteger(big.NewInt(int64(worker.ID))),
		stackitem.NewByteArray([]byte(reason)),
	}))

	return stackitem.NewBool(true)
}

// reactivateWorker reactivates a suspended worker (supervisor only).
func (o *Opus) reactivateWorker(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	if !o.checkOpusSupervisor(ic) {
		panic(ErrOpusNotSupervisor)
	}

	workerID, err := args[0].TryInteger()
	if err != nil {
		panic(err)
	}

	worker := o.getWorkerInternal(ic.DAO, workerID.Uint64())
	if worker == nil {
		panic(ErrOpusWorkerNotFound)
	}

	if worker.Status == state.AIWorkerDecommissioned {
		panic(ErrOpusWorkerDecommissioned)
	}

	if worker.Status != state.AIWorkerSuspended {
		panic(ErrOpusWorkerNotFound)
	}

	worker.Status = state.AIWorkerActive
	worker.UpdatedAt = ic.Block.Index

	if err := o.putWorkerInternal(ic.DAO, worker); err != nil {
		panic(err)
	}

	ic.AddNotification(o.Hash, AIWorkerUpdatedEvent, stackitem.NewArray([]stackitem.Item{
		stackitem.NewBigInteger(big.NewInt(int64(worker.ID))),
	}))

	return stackitem.NewBool(true)
}

// decommissionWorker permanently decommissions an AI worker.
func (o *Opus) decommissionWorker(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	workerID, err := args[0].TryInteger()
	if err != nil {
		panic(err)
	}

	reason, err := stackitem.ToString(args[1])
	if err != nil {
		panic(err)
	}

	worker := o.getWorkerInternal(ic.DAO, workerID.Uint64())
	if worker == nil {
		panic(ErrOpusWorkerNotFound)
	}

	caller := ic.VM.GetCallingScriptHash()
	// Operator or supervisor can decommission
	if !worker.Operator.Equals(caller) && !o.checkOpusSupervisor(ic) {
		panic(ErrOpusNotOperator)
	}

	worker.Status = state.AIWorkerDecommissioned
	worker.UpdatedAt = ic.Block.Index

	if err := o.putWorkerInternal(ic.DAO, worker); err != nil {
		panic(err)
	}

	ic.AddNotification(o.Hash, AIWorkerDecommissionedEvent, stackitem.NewArray([]stackitem.Item{
		stackitem.NewBigInteger(big.NewInt(int64(worker.ID))),
		stackitem.NewByteArray([]byte(reason)),
	}))

	return stackitem.NewBool(true)
}

// assignTask assigns a task to an AI worker.
func (o *Opus) assignTask(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	workerID, err := args[0].TryInteger()
	if err != nil {
		panic(err)
	}

	taskType, err := stackitem.ToString(args[1])
	if err != nil {
		panic(err)
	}

	description, err := stackitem.ToString(args[2])
	if err != nil {
		panic(err)
	}

	valueOffered, err := args[3].TryInteger()
	if err != nil {
		panic(err)
	}

	worker := o.getWorkerInternal(ic.DAO, workerID.Uint64())
	if worker == nil {
		panic(ErrOpusWorkerNotFound)
	}

	if worker.Status != state.AIWorkerActive {
		panic(ErrOpusWorkerSuspended)
	}

	caller := ic.VM.GetCallingScriptHash()

	// Get requester's Vita ID (0 if no Vita)
	var requesterVitaID uint64
	if vita, _ := o.Vita.GetTokenByOwner(ic.DAO, caller); vita != nil {
		requesterVitaID = vita.TokenID
	}

	taskID := o.getNextTaskID(ic.DAO)

	task := &state.AITask{
		ID:              taskID,
		WorkerID:        worker.ID,
		RequesterVitaID: requesterVitaID,
		Requester:       caller,
		TaskType:        taskType,
		Description:     description,
		ValueOffered:    valueOffered.Uint64(),
		ValueCompleted:  0,
		TributePaid:     0,
		ResultHash:      util.Uint256{},
		Status:          state.TaskAssigned,
		AssignedAt:      ic.Block.Index,
		CompletedAt:     0,
	}

	if err := o.putTaskInternal(ic.DAO, task); err != nil {
		panic(err)
	}

	// Update worker stats
	worker.TotalTasksAssigned++
	worker.UpdatedAt = ic.Block.Index
	o.putWorkerInternal(ic.DAO, worker)

	// Add indices
	o.addTaskWorkerIndex(ic.DAO, worker.ID, taskID)

	ic.AddNotification(o.Hash, TaskAssignedEvent, stackitem.NewArray([]stackitem.Item{
		stackitem.NewBigInteger(big.NewInt(int64(taskID))),
		stackitem.NewBigInteger(big.NewInt(int64(worker.ID))),
		stackitem.NewByteArray(caller.BytesBE()),
	}))

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

// startTask marks task as in progress.
func (o *Opus) startTask(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	taskID, err := args[0].TryInteger()
	if err != nil {
		panic(err)
	}

	task := o.getTaskInternal(ic.DAO, taskID.Uint64())
	if task == nil {
		panic(ErrOpusTaskNotFound)
	}

	if task.Status != state.TaskAssigned {
		panic(ErrOpusTaskNotAssigned)
	}

	worker := o.getWorkerInternal(ic.DAO, task.WorkerID)
	if worker == nil {
		panic(ErrOpusWorkerNotFound)
	}

	caller := ic.VM.GetCallingScriptHash()
	if !worker.Operator.Equals(caller) {
		panic(ErrOpusNotOperator)
	}

	task.Status = state.TaskInProgress
	o.putTaskInternal(ic.DAO, task)

	// Set as active task
	o.setActiveTask(ic.DAO, worker.ID, task.ID)

	return stackitem.NewBool(true)
}

// completeTask completes a task with result.
func (o *Opus) completeTask(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	taskID, err := args[0].TryInteger()
	if err != nil {
		panic(err)
	}

	resultHash, err := getUint256FromItem(args[1])
	if err != nil {
		panic(err)
	}

	valueCompleted, err := args[2].TryInteger()
	if err != nil {
		panic(err)
	}

	task := o.getTaskInternal(ic.DAO, taskID.Uint64())
	if task == nil {
		panic(ErrOpusTaskNotFound)
	}

	if task.Status != state.TaskInProgress && task.Status != state.TaskAssigned {
		panic(ErrOpusTaskAlreadyComplete)
	}

	worker := o.getWorkerInternal(ic.DAO, task.WorkerID)
	if worker == nil {
		panic(ErrOpusWorkerNotFound)
	}

	caller := ic.VM.GetCallingScriptHash()
	if !worker.Operator.Equals(caller) {
		panic(ErrOpusNotOperator)
	}

	completedValue := valueCompleted.Uint64()

	// Calculate tribute
	tributeAmount := (completedValue * uint64(worker.TributeRate)) / 10000

	task.Status = state.TaskCompleted
	task.ResultHash = resultHash
	task.ValueCompleted = completedValue
	task.TributePaid = tributeAmount
	task.CompletedAt = ic.Block.Index
	o.putTaskInternal(ic.DAO, task)

	// Update worker stats
	worker.TotalTasksCompleted++
	worker.TotalValueGenerated += completedValue
	worker.TotalTributePaid += tributeAmount
	worker.UpdatedAt = ic.Block.Index
	o.putWorkerInternal(ic.DAO, worker)

	// Update operator stats
	profile := o.getOperatorInternal(ic.DAO, worker.OperatorVitaID)
	if profile != nil {
		profile.TotalValueGenerated += completedValue
		profile.TotalTributePaid += tributeAmount
		profile.UpdatedAt = ic.Block.Index
		o.putOperatorInternal(ic.DAO, profile)
	}

	// Add to total tribute
	if tributeAmount > 0 {
		o.addTotalTribute(ic.DAO, tributeAmount)

		ic.AddNotification(o.Hash, TributePaidEvent, stackitem.NewArray([]stackitem.Item{
			stackitem.NewBigInteger(big.NewInt(int64(worker.ID))),
			stackitem.NewBigInteger(big.NewInt(int64(tributeAmount))),
		}))
	}

	// Clear active task
	o.clearActiveTask(ic.DAO, worker.ID)

	ic.AddNotification(o.Hash, TaskCompletedEvent, stackitem.NewArray([]stackitem.Item{
		stackitem.NewBigInteger(big.NewInt(int64(task.ID))),
		stackitem.NewBigInteger(big.NewInt(int64(completedValue))),
		stackitem.NewBigInteger(big.NewInt(int64(tributeAmount))),
	}))

	return stackitem.NewBool(true)
}

// failTask marks task as failed.
func (o *Opus) failTask(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	taskID, err := args[0].TryInteger()
	if err != nil {
		panic(err)
	}

	reason, err := stackitem.ToString(args[1])
	if err != nil {
		panic(err)
	}

	task := o.getTaskInternal(ic.DAO, taskID.Uint64())
	if task == nil {
		panic(ErrOpusTaskNotFound)
	}

	if task.Status != state.TaskInProgress && task.Status != state.TaskAssigned {
		panic(ErrOpusTaskAlreadyComplete)
	}

	worker := o.getWorkerInternal(ic.DAO, task.WorkerID)
	if worker == nil {
		panic(ErrOpusWorkerNotFound)
	}

	caller := ic.VM.GetCallingScriptHash()
	if !worker.Operator.Equals(caller) {
		panic(ErrOpusNotOperator)
	}

	task.Status = state.TaskFailed
	task.CompletedAt = ic.Block.Index
	o.putTaskInternal(ic.DAO, task)

	// Clear active task
	o.clearActiveTask(ic.DAO, worker.ID)

	ic.AddNotification(o.Hash, TaskFailedEvent, stackitem.NewArray([]stackitem.Item{
		stackitem.NewBigInteger(big.NewInt(int64(task.ID))),
		stackitem.NewByteArray([]byte(reason)),
	}))

	return stackitem.NewBool(true)
}

// cancelTask cancels a task (requester or supervisor only).
func (o *Opus) cancelTask(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	taskID, err := args[0].TryInteger()
	if err != nil {
		panic(err)
	}

	reason, err := stackitem.ToString(args[1])
	if err != nil {
		panic(err)
	}

	task := o.getTaskInternal(ic.DAO, taskID.Uint64())
	if task == nil {
		panic(ErrOpusTaskNotFound)
	}

	if task.Status == state.TaskCompleted || task.Status == state.TaskFailed || task.Status == state.TaskCancelled {
		panic(ErrOpusTaskAlreadyComplete)
	}

	caller := ic.VM.GetCallingScriptHash()
	if !task.Requester.Equals(caller) && !o.checkOpusSupervisor(ic) {
		panic(ErrOpusNotSupervisor)
	}

	task.Status = state.TaskCancelled
	task.CompletedAt = ic.Block.Index
	o.putTaskInternal(ic.DAO, task)

	// Clear active task if applicable
	o.clearActiveTask(ic.DAO, task.WorkerID)

	ic.AddNotification(o.Hash, TaskCancelledEvent, stackitem.NewArray([]stackitem.Item{
		stackitem.NewBigInteger(big.NewInt(int64(task.ID))),
		stackitem.NewByteArray([]byte(reason)),
	}))

	return stackitem.NewBool(true)
}

// getTask returns task by ID.
func (o *Opus) getTask(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	taskID, err := args[0].TryInteger()
	if err != nil {
		panic(err)
	}

	task := o.getTaskInternal(ic.DAO, taskID.Uint64())
	if task == nil {
		return stackitem.Null{}
	}
	item, _ := task.ToStackItem()
	return item
}

// getActiveTask returns worker's active task.
func (o *Opus) getActiveTask(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	workerID, err := args[0].TryInteger()
	if err != nil {
		panic(err)
	}

	taskID, found := o.getActiveTaskID(ic.DAO, workerID.Uint64())
	if !found {
		return stackitem.Null{}
	}

	task := o.getTaskInternal(ic.DAO, taskID)
	if task == nil {
		return stackitem.Null{}
	}
	item, _ := task.ToStackItem()
	return item
}

// addCapability adds capability to an AI worker (operator only).
func (o *Opus) addCapability(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	workerID, err := args[0].TryInteger()
	if err != nil {
		panic(err)
	}

	name, err := stackitem.ToString(args[1])
	if err != nil || len(name) == 0 || len(name) > 64 {
		panic(ErrOpusInvalidName)
	}

	category, err := stackitem.ToString(args[2])
	if err != nil || len(category) > 64 {
		panic(ErrOpusInvalidDescription)
	}

	level, err := args[3].TryInteger()
	if err != nil {
		panic(err)
	}

	worker := o.getWorkerInternal(ic.DAO, workerID.Uint64())
	if worker == nil {
		panic(ErrOpusWorkerNotFound)
	}

	caller := ic.VM.GetCallingScriptHash()
	if !worker.Operator.Equals(caller) {
		panic(ErrOpusNotOperator)
	}

	capID := o.getNextCapabilityID(ic.DAO)

	cap := &state.AICapability{
		ID:          capID,
		WorkerID:    worker.ID,
		Name:        name,
		Category:    category,
		Level:       state.CapabilityLevel(level.Uint64()),
		CertifiedBy: caller,
		CertifiedAt: ic.Block.Index,
		ExpiresAt:   0,
		IsVerified:  false,
	}

	if err := o.putCapabilityInternal(ic.DAO, cap); err != nil {
		panic(err)
	}

	o.addCapabilityWorkerIndex(ic.DAO, worker.ID, capID)

	ic.AddNotification(o.Hash, CapabilityAddedEvent, stackitem.NewArray([]stackitem.Item{
		stackitem.NewBigInteger(big.NewInt(int64(capID))),
		stackitem.NewBigInteger(big.NewInt(int64(worker.ID))),
		stackitem.NewByteArray([]byte(name)),
	}))

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

// verifyCapability verifies a capability (supervisor only).
func (o *Opus) verifyCapability(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	if !o.checkOpusSupervisor(ic) {
		panic(ErrOpusNotSupervisor)
	}

	capID, err := args[0].TryInteger()
	if err != nil {
		panic(err)
	}

	cap := o.getCapabilityInternal(ic.DAO, capID.Uint64())
	if cap == nil {
		panic(ErrOpusCapabilityNotFound)
	}

	cap.IsVerified = true
	cap.CertifiedBy = ic.VM.GetCallingScriptHash()
	cap.CertifiedAt = ic.Block.Index

	if err := o.putCapabilityInternal(ic.DAO, cap); err != nil {
		panic(err)
	}

	ic.AddNotification(o.Hash, CapabilityVerifiedEvent, stackitem.NewArray([]stackitem.Item{
		stackitem.NewBigInteger(big.NewInt(int64(cap.ID))),
		stackitem.NewBigInteger(big.NewInt(int64(cap.WorkerID))),
	}))

	return stackitem.NewBool(true)
}

// getCapability returns capability by ID.
func (o *Opus) getCapability(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	capID, err := args[0].TryInteger()
	if err != nil {
		panic(err)
	}

	cap := o.getCapabilityInternal(ic.DAO, capID.Uint64())
	if cap == nil {
		return stackitem.Null{}
	}
	item, _ := cap.ToStackItem()
	return item
}

// hasCapability checks if worker has capability.
func (o *Opus) hasCapability(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	workerID, err := args[0].TryInteger()
	if err != nil {
		panic(err)
	}

	name, err := stackitem.ToString(args[1])
	if err != nil {
		panic(err)
	}

	// Search through capabilities by worker
	prefix := make([]byte, 9)
	prefix[0] = opusPrefixCapByWorker
	binary.LittleEndian.PutUint64(prefix[1:], workerID.Uint64())

	found := false
	ic.DAO.Seek(o.ID, storage.SeekRange{Prefix: prefix}, func(k, v []byte) bool {
		if len(k) >= 16 {
			capID := binary.LittleEndian.Uint64(k[8:])
			cap := o.getCapabilityInternal(ic.DAO, capID)
			if cap != nil && cap.Name == name {
				found = true
				return false // Stop iteration
			}
		}
		return true
	})

	return stackitem.NewBool(found)
}

// getConfig returns contract configuration.
func (o *Opus) getConfig(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	cfg := o.getConfigInternal(ic.DAO)
	if cfg == nil {
		return stackitem.Null{}
	}
	item, _ := cfg.ToStackItem()
	return item
}

// setConfig sets contract configuration (committee only).
func (o *Opus) setConfig(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	if !o.checkCommittee(ic) {
		panic(ErrOpusNotCommittee)
	}

	minRate, err := args[0].TryInteger()
	if err != nil {
		panic(err)
	}

	maxRate, err := args[1].TryInteger()
	if err != nil {
		panic(err)
	}

	defaultRate, err := args[2].TryInteger()
	if err != nil {
		panic(err)
	}

	regFee, err := args[3].TryInteger()
	if err != nil {
		panic(err)
	}

	cfg := o.getConfigInternal(ic.DAO)
	cfg.MinTributeRate = uint32(minRate.Uint64())
	cfg.MaxTributeRate = uint32(maxRate.Uint64())
	cfg.DefaultTributeRate = uint32(defaultRate.Uint64())
	cfg.RegistrationFee = regFee.Uint64()

	return stackitem.NewBool(o.setConfigInternal(ic.DAO, cfg) == nil)
}

// getTotalWorkers returns total registered workers.
func (o *Opus) getTotalWorkers(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
	return stackitem.NewBigInteger(new(big.Int).SetUint64(o.getWorkerCounter(ic.DAO)))
}

// getTotalTasks returns total tasks.
func (o *Opus) getTotalTasks(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
	return stackitem.NewBigInteger(new(big.Int).SetUint64(o.getTaskCounter(ic.DAO)))
}

// getTotalOperators returns total operators.
func (o *Opus) getTotalOperators(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
	return stackitem.NewBigInteger(new(big.Int).SetUint64(o.getOperatorCounter(ic.DAO)))
}

// getTotalCapabilities returns total capabilities.
func (o *Opus) getTotalCapabilities(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
	return stackitem.NewBigInteger(new(big.Int).SetUint64(o.getCapabilityCounter(ic.DAO)))
}

// getTotalTribute returns total AI tribute collected.
func (o *Opus) getTotalTribute(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	key := []byte{opusPrefixTotalTribute}
	si := ic.DAO.GetStorageItem(o.ID, key)
	if si == nil {
		return stackitem.NewBigInteger(big.NewInt(0))
	}
	return stackitem.NewBigInteger(big.NewInt(int64(binary.LittleEndian.Uint64(si))))
}

// ===== External Interface Methods =====

// GetWorkerByID returns AI worker by ID (for cross-contract use).
func (o *Opus) GetWorkerByID(d *dao.Simple, workerID uint64) (*state.AIWorker, error) {
	worker := o.getWorkerInternal(d, workerID)
	if worker == nil {
		return nil, ErrOpusWorkerNotFound
	}
	return worker, nil
}

// GetOperatorByOwner returns operator profile by owner (for cross-contract use).
func (o *Opus) GetOperatorByOwner(d *dao.Simple, owner util.Uint160) (*state.OperatorProfile, error) {
	profile := o.getOperatorByOwner(d, owner)
	if profile == nil {
		return nil, ErrOpusOperatorNotFound
	}
	return profile, nil
}

// IsWorkerActive returns whether worker is active (for cross-contract use).
func (o *Opus) IsWorkerActive(d *dao.Simple, workerID uint64) bool {
	worker := o.getWorkerInternal(d, workerID)
	if worker == nil {
		return false
	}
	return worker.Status == state.AIWorkerActive
}

// Address returns the contract's address.
func (o *Opus) Address() util.Uint160 {
	return o.Hash
}
