package native

import (
	"encoding/binary"
	"errors"
	"fmt"
	"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"
)

// Lex represents the Lex native contract for universal law and rights enforcement.
// It provides:
// - Immutable constitutional rights (14 fundamental rights)
// - Hierarchical law registry (Federal > Regional > Local)
// - Rights restriction system (judicial orders)
// - Cross-contract compliance checking
type Lex struct {
	interop.ContractMD
	Tutus ITutus
	Vita         IVita
	RoleRegistry IRoleRegistry
	Federation   IFederation
}

// Storage key prefixes for Lex.
const (
	lexPrefixLaw             byte = 0x01 // lawID -> Law
	lexPrefixLawName         byte = 0x02 // name -> lawID
	lexPrefixLawCategory     byte = 0x03 // category + lawID -> exists
	lexPrefixLawJurisdiction byte = 0x04 // jurisdiction + lawID -> exists
	lexPrefixLawHierarchy    byte = 0x05 // parentID + childID -> exists
	lexPrefixRestriction     byte = 0x20 // subject + rightID -> RightsRestriction
	lexPrefixSubjectRights   byte = 0x21 // subject -> []restrictedRightIDs
	lexPrefixLawCounter      byte = 0xF0 // -> nextLawID
)

// Maximum lengths.
const (
	maxLawNameLength   = 256
	maxReasonLength    = 1024
	maxLawsPerCategory = 10000
)

// Event names for Lex.
const (
	LawEnactedEvent        = "LawEnacted"
	LawAmendedEvent        = "LawAmended"
	LawRepealedEvent       = "LawRepealed"
	LawSuspendedEvent      = "LawSuspended"
	LawReinstatedEvent     = "LawReinstated"
	RightRestrictedEvent   = "RightRestricted"
	RestrictionLiftedEvent = "RestrictionLifted"
	RightViolationEvent    = "RightViolation"
)

// Judicial authority roles (defined in RoleRegistry).
const (
	RoleLegislator uint64 = 10 // Can propose and enact laws
	RoleJudge      uint64 = 11 // Can issue rights restrictions
	RoleProsecutor uint64 = 12 // Can initiate cases
	RoleDefender   uint64 = 13 // Public defender
	RoleEnforcer   uint64 = 14 // Can execute judicial orders
)

// Various errors.
var (
	ErrLawNotFound          = errors.New("law not found")
	ErrLawNameExists        = errors.New("law name already exists")
	ErrLawNameTooLong       = errors.New("law name too long")
	ErrInvalidLawCategory   = errors.New("invalid law category")
	ErrInvalidParentLaw     = errors.New("invalid parent law")
	ErrLawNotActive         = errors.New("law is not active")
	ErrCannotModifyConst    = errors.New("cannot modify constitutional rights")
	ErrRestrictionNotFound  = errors.New("restriction not found")
	ErrRestrictionExists    = errors.New("restriction already exists")
	ErrInvalidRightID       = errors.New("invalid right ID")
	ErrNoCaseID             = errors.New("case ID required for due process")
	ErrNoExpiration         = errors.New("expiration required (no indefinite restrictions)")
	ErrNotAuthorized        = errors.New("not authorized")
	ErrRightRestricted      = errors.New("right is restricted")
	ErrNoActiveVita         = errors.New("no active Vita token")
	ErrJudicialAuthRequired = errors.New("judicial authority required")
)

var _ interop.Contract = (*Lex)(nil)

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

	// Query methods (safe, read-only)

	// getLaw returns a law by ID
	desc := NewDescriptor("getLaw", smartcontract.ArrayType,
		manifest.NewParameter("lawID", smartcontract.IntegerType))
	md := NewMethodAndPrice(l.getLaw, 1<<15, callflag.ReadStates)
	l.AddMethod(md, desc)

	// getLawByName returns a law by name
	desc = NewDescriptor("getLawByName", smartcontract.ArrayType,
		manifest.NewParameter("name", smartcontract.StringType))
	md = NewMethodAndPrice(l.getLawByName, 1<<15, callflag.ReadStates)
	l.AddMethod(md, desc)

	// isLawActive checks if a law is currently active
	desc = NewDescriptor("isLawActive", smartcontract.BoolType,
		manifest.NewParameter("lawID", smartcontract.IntegerType))
	md = NewMethodAndPrice(l.isLawActive, 1<<15, callflag.ReadStates)
	l.AddMethod(md, desc)

	// getConstitutionalRight returns a constitutional right by ID
	desc = NewDescriptor("getConstitutionalRight", smartcontract.ArrayType,
		manifest.NewParameter("rightID", smartcontract.IntegerType))
	md = NewMethodAndPrice(l.getConstitutionalRight, 1<<15, callflag.ReadStates)
	l.AddMethod(md, desc)

	// getAllConstitutionalRights returns all 14 constitutional rights
	desc = NewDescriptor("getAllConstitutionalRights", smartcontract.ArrayType)
	md = NewMethodAndPrice(l.getAllConstitutionalRights, 1<<15, callflag.ReadStates)
	l.AddMethod(md, desc)

	// hasRight checks if a subject has a specific right (not restricted)
	desc = NewDescriptor("hasRight", smartcontract.BoolType,
		manifest.NewParameter("subject", smartcontract.Hash160Type),
		manifest.NewParameter("rightID", smartcontract.IntegerType))
	md = NewMethodAndPrice(l.hasRight, 1<<15, callflag.ReadStates)
	l.AddMethod(md, desc)

	// isRestricted checks if a subject's right is restricted
	desc = NewDescriptor("isRestricted", smartcontract.BoolType,
		manifest.NewParameter("subject", smartcontract.Hash160Type),
		manifest.NewParameter("rightID", smartcontract.IntegerType))
	md = NewMethodAndPrice(l.isRestricted, 1<<15, callflag.ReadStates)
	l.AddMethod(md, desc)

	// getRestriction returns the restriction on a subject's right
	desc = NewDescriptor("getRestriction", smartcontract.ArrayType,
		manifest.NewParameter("subject", smartcontract.Hash160Type),
		manifest.NewParameter("rightID", smartcontract.IntegerType))
	md = NewMethodAndPrice(l.getRestriction, 1<<15, callflag.ReadStates)
	l.AddMethod(md, desc)

	// getSubjectRestrictions returns all restrictions on a subject
	desc = NewDescriptor("getSubjectRestrictions", smartcontract.ArrayType,
		manifest.NewParameter("subject", smartcontract.Hash160Type))
	md = NewMethodAndPrice(l.getSubjectRestrictions, 1<<16, callflag.ReadStates)
	l.AddMethod(md, desc)

	// getLawCount returns the total number of laws
	desc = NewDescriptor("getLawCount", smartcontract.IntegerType)
	md = NewMethodAndPrice(l.getLawCount, 1<<15, callflag.ReadStates)
	l.AddMethod(md, desc)

	// Administrative methods (committee/authority only)

	// enactLaw creates a new law
	desc = NewDescriptor("enactLaw", smartcontract.IntegerType,
		manifest.NewParameter("name", smartcontract.StringType),
		manifest.NewParameter("contentHash", smartcontract.Hash256Type),
		manifest.NewParameter("category", smartcontract.IntegerType),
		manifest.NewParameter("jurisdiction", smartcontract.IntegerType),
		manifest.NewParameter("parentID", smartcontract.IntegerType),
		manifest.NewParameter("effectiveAt", smartcontract.IntegerType),
		manifest.NewParameter("enforcement", smartcontract.IntegerType))
	md = NewMethodAndPrice(l.enactLaw, 1<<17, callflag.States|callflag.AllowNotify)
	l.AddMethod(md, desc)

	// repealLaw permanently removes a law
	desc = NewDescriptor("repealLaw", smartcontract.BoolType,
		manifest.NewParameter("lawID", smartcontract.IntegerType),
		manifest.NewParameter("reason", smartcontract.StringType))
	md = NewMethodAndPrice(l.repealLaw, 1<<16, callflag.States|callflag.AllowNotify)
	l.AddMethod(md, desc)

	// suspendLaw temporarily suspends a law
	desc = NewDescriptor("suspendLaw", smartcontract.BoolType,
		manifest.NewParameter("lawID", smartcontract.IntegerType),
		manifest.NewParameter("reason", smartcontract.StringType),
		manifest.NewParameter("duration", smartcontract.IntegerType))
	md = NewMethodAndPrice(l.suspendLaw, 1<<16, callflag.States|callflag.AllowNotify)
	l.AddMethod(md, desc)

	// reinstateLaw reinstates a suspended law
	desc = NewDescriptor("reinstateLaw", smartcontract.BoolType,
		manifest.NewParameter("lawID", smartcontract.IntegerType))
	md = NewMethodAndPrice(l.reinstateLaw, 1<<16, callflag.States|callflag.AllowNotify)
	l.AddMethod(md, desc)

	// Rights restriction methods (judicial authority only)

	// restrictRight restricts a subject's right
	desc = NewDescriptor("restrictRight", smartcontract.BoolType,
		manifest.NewParameter("subject", smartcontract.Hash160Type),
		manifest.NewParameter("rightID", smartcontract.IntegerType),
		manifest.NewParameter("restrictionType", smartcontract.IntegerType),
		manifest.NewParameter("duration", smartcontract.IntegerType),
		manifest.NewParameter("reason", smartcontract.StringType),
		manifest.NewParameter("caseID", smartcontract.Hash256Type))
	md = NewMethodAndPrice(l.restrictRight, 1<<17, callflag.States|callflag.AllowNotify)
	l.AddMethod(md, desc)

	// liftRestriction removes a restriction
	desc = NewDescriptor("liftRestriction", smartcontract.BoolType,
		manifest.NewParameter("subject", smartcontract.Hash160Type),
		manifest.NewParameter("rightID", smartcontract.IntegerType),
		manifest.NewParameter("reason", smartcontract.StringType))
	md = NewMethodAndPrice(l.liftRestriction, 1<<16, callflag.States|callflag.AllowNotify)
	l.AddMethod(md, desc)

	// Events
	eDesc := NewEventDescriptor(LawEnactedEvent,
		manifest.NewParameter("lawID", smartcontract.IntegerType),
		manifest.NewParameter("name", smartcontract.StringType),
		manifest.NewParameter("category", smartcontract.IntegerType),
		manifest.NewParameter("enactedBy", smartcontract.Hash160Type))
	l.AddEvent(NewEvent(eDesc))

	eDesc = NewEventDescriptor(LawRepealedEvent,
		manifest.NewParameter("lawID", smartcontract.IntegerType),
		manifest.NewParameter("reason", smartcontract.StringType))
	l.AddEvent(NewEvent(eDesc))

	eDesc = NewEventDescriptor(LawSuspendedEvent,
		manifest.NewParameter("lawID", smartcontract.IntegerType),
		manifest.NewParameter("reason", smartcontract.StringType),
		manifest.NewParameter("duration", smartcontract.IntegerType))
	l.AddEvent(NewEvent(eDesc))

	eDesc = NewEventDescriptor(LawReinstatedEvent,
		manifest.NewParameter("lawID", smartcontract.IntegerType))
	l.AddEvent(NewEvent(eDesc))

	eDesc = NewEventDescriptor(RightRestrictedEvent,
		manifest.NewParameter("subject", smartcontract.Hash160Type),
		manifest.NewParameter("rightID", smartcontract.IntegerType),
		manifest.NewParameter("restrictionType", smartcontract.IntegerType),
		manifest.NewParameter("expiresAt", smartcontract.IntegerType),
		manifest.NewParameter("caseID", smartcontract.Hash256Type))
	l.AddEvent(NewEvent(eDesc))

	eDesc = NewEventDescriptor(RestrictionLiftedEvent,
		manifest.NewParameter("subject", smartcontract.Hash160Type),
		manifest.NewParameter("rightID", smartcontract.IntegerType),
		manifest.NewParameter("reason", smartcontract.StringType))
	l.AddEvent(NewEvent(eDesc))

	eDesc = NewEventDescriptor(RightViolationEvent,
		manifest.NewParameter("subject", smartcontract.Hash160Type),
		manifest.NewParameter("rightID", smartcontract.IntegerType),
		manifest.NewParameter("action", smartcontract.StringType))
	l.AddEvent(NewEvent(eDesc))

	return l
}

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

// Initialize implements the Contract interface.
func (l *Lex) Initialize(ic *interop.Context, hf *config.Hardfork, newMD *interop.HFSpecificContractMD) error {
	if hf != l.ActiveIn() {
		return nil
	}
	// Initialize law counter starting at 1 (0 is reserved for root)
	l.putLawCounter(ic.DAO, 1)
	return nil
}

// InitializeCache implements the Contract interface.
func (l *Lex) InitializeCache(_ interop.IsHardforkEnabled, blockHeight uint32, d *dao.Simple) error {
	return nil
}

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

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

// ActiveIn implements the Contract interface.
func (l *Lex) ActiveIn() *config.Hardfork {
	return nil
}

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

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

func (l *Lex) makeLawKey(lawID uint64) []byte {
	key := make([]byte, 9)
	key[0] = lexPrefixLaw
	binary.BigEndian.PutUint64(key[1:], lawID)
	return key
}

func (l *Lex) makeLawNameKey(name string) []byte {
	key := make([]byte, 1+len(name))
	key[0] = lexPrefixLawName
	copy(key[1:], name)
	return key
}

func (l *Lex) makeRestrictionKey(subject util.Uint160, rightID uint64) []byte {
	key := make([]byte, 29)
	key[0] = lexPrefixRestriction
	copy(key[1:21], subject[:])
	binary.BigEndian.PutUint64(key[21:], rightID)
	return key
}

func (l *Lex) makeSubjectRightsKey(subject util.Uint160) []byte {
	key := make([]byte, 21)
	key[0] = lexPrefixSubjectRights
	copy(key[1:], subject[:])
	return key
}

// ==================== Law Counter ====================

func (l *Lex) getLawCounter(d *dao.Simple) uint64 {
	si := d.GetStorageItem(l.ID, []byte{lexPrefixLawCounter})
	if si == nil {
		return 1
	}
	return binary.BigEndian.Uint64(si)
}

func (l *Lex) putLawCounter(d *dao.Simple, count uint64) {
	buf := make([]byte, 8)
	binary.BigEndian.PutUint64(buf, count)
	d.PutStorageItem(l.ID, []byte{lexPrefixLawCounter}, buf)
}

// ==================== Internal Storage Methods ====================

func (l *Lex) getLawInternal(d *dao.Simple, lawID uint64) *state.Law {
	si := d.GetStorageItem(l.ID, l.makeLawKey(lawID))
	if si == nil {
		return nil
	}
	law, err := state.LawFromBytes(si)
	if err != nil {
		return nil
	}
	return law
}

func (l *Lex) putLaw(d *dao.Simple, law *state.Law) {
	d.PutStorageItem(l.ID, l.makeLawKey(law.ID), law.Bytes())
}

func (l *Lex) getLawIDByName(d *dao.Simple, name string) (uint64, bool) {
	si := d.GetStorageItem(l.ID, l.makeLawNameKey(name))
	if si == nil {
		return 0, false
	}
	return binary.BigEndian.Uint64(si), true
}

func (l *Lex) putLawNameIndex(d *dao.Simple, name string, lawID uint64) {
	buf := make([]byte, 8)
	binary.BigEndian.PutUint64(buf, lawID)
	d.PutStorageItem(l.ID, l.makeLawNameKey(name), buf)
}

func (l *Lex) getRestrictionInternal(d *dao.Simple, subject util.Uint160, rightID uint64) *state.RightsRestriction {
	si := d.GetStorageItem(l.ID, l.makeRestrictionKey(subject, rightID))
	if si == nil {
		return nil
	}
	r, err := state.RightsRestrictionFromBytes(si)
	if err != nil {
		return nil
	}
	return r
}

func (l *Lex) putRestriction(d *dao.Simple, r *state.RightsRestriction) {
	d.PutStorageItem(l.ID, l.makeRestrictionKey(r.Subject, r.RightID), r.Bytes())
}

func (l *Lex) deleteRestriction(d *dao.Simple, subject util.Uint160, rightID uint64) {
	d.DeleteStorageItem(l.ID, l.makeRestrictionKey(subject, rightID))
}

func (l *Lex) getSubjectRestrictedRights(d *dao.Simple, subject util.Uint160) []uint64 {
	si := d.GetStorageItem(l.ID, l.makeSubjectRightsKey(subject))
	if si == nil {
		return nil
	}
	count := len(si) / 8
	rights := make([]uint64, count)
	for i := 0; i < count; i++ {
		rights[i] = binary.BigEndian.Uint64(si[i*8:])
	}
	return rights
}

func (l *Lex) putSubjectRestrictedRights(d *dao.Simple, subject util.Uint160, rights []uint64) {
	if len(rights) == 0 {
		d.DeleteStorageItem(l.ID, l.makeSubjectRightsKey(subject))
		return
	}
	buf := make([]byte, len(rights)*8)
	for i, r := range rights {
		binary.BigEndian.PutUint64(buf[i*8:], r)
	}
	d.PutStorageItem(l.ID, l.makeSubjectRightsKey(subject), buf)
}

func (l *Lex) addSubjectRestrictedRight(d *dao.Simple, subject util.Uint160, rightID uint64) {
	rights := l.getSubjectRestrictedRights(d, subject)
	for _, r := range rights {
		if r == rightID {
			return // Already exists
		}
	}
	rights = append(rights, rightID)
	l.putSubjectRestrictedRights(d, subject, rights)
}

func (l *Lex) removeSubjectRestrictedRight(d *dao.Simple, subject util.Uint160, rightID uint64) {
	rights := l.getSubjectRestrictedRights(d, subject)
	for i, r := range rights {
		if r == rightID {
			rights = append(rights[:i], rights[i+1:]...)
			break
		}
	}
	l.putSubjectRestrictedRights(d, subject, rights)
}

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

func (l *Lex) checkCommittee(ic *interop.Context) error {
	if l.RoleRegistry != nil && l.RoleRegistry.CheckCommittee(ic) {
		return nil
	}
	if l.Tutus != nil && l.Tutus.CheckCommittee(ic) {
		return nil
	}
	return ErrNotAuthorized
}

func (l *Lex) checkJudicialAuthority(ic *interop.Context) error {
	if l.RoleRegistry == nil {
		// Fall back to committee check if no RoleRegistry
		return l.checkCommittee(ic)
	}
	caller := ic.VM.GetCallingScriptHash()
	if l.RoleRegistry.HasRoleInternal(ic.DAO, caller, RoleJudge, ic.Block.Index) {
		return nil
	}
	// Also allow committee
	if l.RoleRegistry.CheckCommittee(ic) {
		return nil
	}
	return ErrJudicialAuthRequired
}

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

func (l *Lex) getLaw(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	lawID := toUint64(args[0])
	law := l.getLawInternal(ic.DAO, lawID)
	if law == nil {
		return stackitem.Null{}
	}
	item, _ := law.ToStackItem()
	return item
}

func (l *Lex) getLawByName(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	name := toString(args[0])
	lawID, found := l.getLawIDByName(ic.DAO, name)
	if !found {
		return stackitem.Null{}
	}
	law := l.getLawInternal(ic.DAO, lawID)
	if law == nil {
		return stackitem.Null{}
	}
	item, _ := law.ToStackItem()
	return item
}

func (l *Lex) isLawActive(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	lawID := toUint64(args[0])
	law := l.getLawInternal(ic.DAO, lawID)
	if law == nil {
		return stackitem.NewBool(false)
	}
	// Check status and effective dates
	if law.Status != state.LawStatusActive {
		return stackitem.NewBool(false)
	}
	if law.EffectiveAt > ic.Block.Index {
		return stackitem.NewBool(false)
	}
	if law.ExpiresAt != 0 && law.ExpiresAt <= ic.Block.Index {
		return stackitem.NewBool(false)
	}
	return stackitem.NewBool(true)
}

func (l *Lex) getConstitutionalRight(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	rightID := toUint64(args[0])
	right := state.GetConstitutionalRight(rightID)
	if right == nil {
		return stackitem.Null{}
	}
	return stackitem.NewStruct([]stackitem.Item{
		stackitem.NewBigInteger(big.NewInt(int64(right.ID))),
		stackitem.NewByteArray([]byte(right.Name)),
		stackitem.NewByteArray([]byte(right.Description)),
		stackitem.NewBigInteger(big.NewInt(int64(right.Enforcement))),
	})
}

func (l *Lex) getAllConstitutionalRights(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	rights := state.GetConstitutionalRights()
	items := make([]stackitem.Item, len(rights))
	for i, r := range rights {
		items[i] = stackitem.NewStruct([]stackitem.Item{
			stackitem.NewBigInteger(big.NewInt(int64(r.ID))),
			stackitem.NewByteArray([]byte(r.Name)),
			stackitem.NewByteArray([]byte(r.Description)),
			stackitem.NewBigInteger(big.NewInt(int64(r.Enforcement))),
		})
	}
	return stackitem.NewArray(items)
}

func (l *Lex) hasRight(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	subject := toUint160(args[0])
	rightID := toUint64(args[1])
	return stackitem.NewBool(l.HasRightInternal(ic.DAO, subject, rightID, ic.Block.Index))
}

func (l *Lex) isRestricted(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	subject := toUint160(args[0])
	rightID := toUint64(args[1])
	return stackitem.NewBool(l.IsRestrictedInternal(ic.DAO, subject, rightID, ic.Block.Index))
}

func (l *Lex) getRestriction(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	subject := toUint160(args[0])
	rightID := toUint64(args[1])
	r := l.getRestrictionInternal(ic.DAO, subject, rightID)
	if r == nil {
		return stackitem.Null{}
	}
	item, _ := r.ToStackItem()
	return item
}

func (l *Lex) getSubjectRestrictions(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	subject := toUint160(args[0])
	rightIDs := l.getSubjectRestrictedRights(ic.DAO, subject)
	items := make([]stackitem.Item, 0)
	for _, rightID := range rightIDs {
		r := l.getRestrictionInternal(ic.DAO, subject, rightID)
		if r != nil && !r.IsExpired(ic.Block.Index) {
			item, _ := r.ToStackItem()
			items = append(items, item)
		}
	}
	return stackitem.NewArray(items)
}

func (l *Lex) getLawCount(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	count := l.getLawCounter(ic.DAO)
	return stackitem.NewBigInteger(big.NewInt(int64(count - 1))) // Counter starts at 1
}

// ==================== Law Management Methods ====================

func (l *Lex) enactLaw(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	if err := l.checkCommittee(ic); err != nil {
		panic(err)
	}

	name := toString(args[0])
	if len(name) > maxLawNameLength {
		panic(ErrLawNameTooLong)
	}

	contentHashBytes, err := args[1].TryBytes()
	if err != nil {
		panic("invalid content hash")
	}
	var contentHash util.Uint256
	if len(contentHashBytes) == 32 {
		copy(contentHash[:], contentHashBytes)
	}

	category := state.LawCategory(toUint64(args[2]))
	if category < state.LawCategoryFederal || category > state.LawCategoryAdministrative {
		panic(ErrInvalidLawCategory)
	}
	// Cannot create constitutional laws via enactLaw - they are immutable
	if category == state.LawCategoryConstitutional {
		panic(ErrCannotModifyConst)
	}

	jurisdiction := uint32(toUint64(args[3]))
	parentID := toUint64(args[4])
	effectiveAt := uint32(toUint64(args[5]))
	enforcement := state.EnforcementType(toUint64(args[6]))

	// Check if name exists
	if _, exists := l.getLawIDByName(ic.DAO, name); exists {
		panic(ErrLawNameExists)
	}

	// Validate parent law if specified
	if parentID != 0 {
		parent := l.getLawInternal(ic.DAO, parentID)
		if parent == nil {
			panic(ErrInvalidParentLaw)
		}
		if parent.Status != state.LawStatusActive {
			panic(ErrLawNotActive)
		}
		// Child category must be lower or equal precedence than parent
		if category < parent.Category {
			panic(ErrInvalidLawCategory)
		}
	}

	// Create law
	lawID := l.getLawCounter(ic.DAO)
	law := &state.Law{
		ID:           lawID,
		Name:         name,
		ContentHash:  contentHash,
		Category:     category,
		Jurisdiction: jurisdiction,
		ParentID:     parentID,
		EffectiveAt:  effectiveAt,
		ExpiresAt:    0, // Perpetual by default
		EnactedAt:    ic.Block.Index,
		EnactedBy:    ic.VM.GetCallingScriptHash(),
		Status:       state.LawStatusActive,
		SupersededBy: 0,
		RequiresVita: true,
		Enforcement:  enforcement,
	}

	l.putLaw(ic.DAO, law)
	l.putLawNameIndex(ic.DAO, name, lawID)
	l.putLawCounter(ic.DAO, lawID+1)

	ic.AddNotification(l.Hash, LawEnactedEvent, stackitem.NewArray([]stackitem.Item{
		stackitem.NewBigInteger(big.NewInt(int64(lawID))),
		stackitem.NewByteArray([]byte(name)),
		stackitem.NewBigInteger(big.NewInt(int64(category))),
		stackitem.NewByteArray(law.EnactedBy.BytesBE()),
	}))

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

func (l *Lex) repealLaw(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	if err := l.checkCommittee(ic); err != nil {
		panic(err)
	}

	lawID := toUint64(args[0])
	reason := toString(args[1])
	if len(reason) > maxReasonLength {
		panic(ErrReasonTooLong)
	}

	law := l.getLawInternal(ic.DAO, lawID)
	if law == nil {
		panic(ErrLawNotFound)
	}
	if law.Category == state.LawCategoryConstitutional {
		panic(ErrCannotModifyConst)
	}
	if law.Status == state.LawStatusRepealed {
		return stackitem.NewBool(false)
	}

	law.Status = state.LawStatusRepealed
	l.putLaw(ic.DAO, law)

	ic.AddNotification(l.Hash, LawRepealedEvent, stackitem.NewArray([]stackitem.Item{
		stackitem.NewBigInteger(big.NewInt(int64(lawID))),
		stackitem.NewByteArray([]byte(reason)),
	}))

	return stackitem.NewBool(true)
}

func (l *Lex) suspendLaw(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	if err := l.checkCommittee(ic); err != nil {
		panic(err)
	}

	lawID := toUint64(args[0])
	reason := toString(args[1])
	duration := uint32(toUint64(args[2]))

	if len(reason) > maxReasonLength {
		panic(ErrReasonTooLong)
	}

	law := l.getLawInternal(ic.DAO, lawID)
	if law == nil {
		panic(ErrLawNotFound)
	}
	if law.Category == state.LawCategoryConstitutional {
		panic(ErrCannotModifyConst)
	}
	if law.Status != state.LawStatusActive {
		panic(ErrLawNotActive)
	}

	law.Status = state.LawStatusSuspended
	l.putLaw(ic.DAO, law)

	ic.AddNotification(l.Hash, LawSuspendedEvent, stackitem.NewArray([]stackitem.Item{
		stackitem.NewBigInteger(big.NewInt(int64(lawID))),
		stackitem.NewByteArray([]byte(reason)),
		stackitem.NewBigInteger(big.NewInt(int64(duration))),
	}))

	return stackitem.NewBool(true)
}

func (l *Lex) reinstateLaw(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	if err := l.checkCommittee(ic); err != nil {
		panic(err)
	}

	lawID := toUint64(args[0])

	law := l.getLawInternal(ic.DAO, lawID)
	if law == nil {
		panic(ErrLawNotFound)
	}
	if law.Status != state.LawStatusSuspended {
		return stackitem.NewBool(false)
	}

	law.Status = state.LawStatusActive
	l.putLaw(ic.DAO, law)

	ic.AddNotification(l.Hash, LawReinstatedEvent, stackitem.NewArray([]stackitem.Item{
		stackitem.NewBigInteger(big.NewInt(int64(lawID))),
	}))

	return stackitem.NewBool(true)
}

// ==================== Rights Restriction Methods ====================

func (l *Lex) restrictRight(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	if err := l.checkJudicialAuthority(ic); err != nil {
		panic(err)
	}

	subject := toUint160(args[0])
	rightID := toUint64(args[1])
	restrictionType := state.RestrictionType(toUint64(args[2]))
	duration := uint32(toUint64(args[3]))
	reason := toString(args[4])

	caseIDBytes, err := args[5].TryBytes()
	if err != nil {
		panic("invalid case ID")
	}
	var caseID util.Uint256
	if len(caseIDBytes) == 32 {
		copy(caseID[:], caseIDBytes)
	}

	// Validate inputs
	if !state.IsConstitutionalRight(rightID) {
		panic(ErrInvalidRightID)
	}
	if len(reason) > maxReasonLength {
		panic(ErrReasonTooLong)
	}
	// Due process requires a case ID
	if caseID == (util.Uint256{}) {
		panic(ErrNoCaseID)
	}
	// No indefinite restrictions allowed
	if duration == 0 {
		panic(ErrNoExpiration)
	}

	// Check if restriction already exists
	existing := l.getRestrictionInternal(ic.DAO, subject, rightID)
	if existing != nil && !existing.IsExpired(ic.Block.Index) {
		panic(ErrRestrictionExists)
	}

	r := &state.RightsRestriction{
		Subject:         subject,
		RightID:         rightID,
		RestrictionType: restrictionType,
		IssuedBy:        ic.VM.GetCallingScriptHash(),
		IssuedAt:        ic.Block.Index,
		ExpiresAt:       ic.Block.Index + duration,
		Reason:          reason,
		CaseID:          caseID,
	}

	l.putRestriction(ic.DAO, r)
	l.addSubjectRestrictedRight(ic.DAO, subject, rightID)

	ic.AddNotification(l.Hash, RightRestrictedEvent, stackitem.NewArray([]stackitem.Item{
		stackitem.NewByteArray(subject.BytesBE()),
		stackitem.NewBigInteger(big.NewInt(int64(rightID))),
		stackitem.NewBigInteger(big.NewInt(int64(restrictionType))),
		stackitem.NewBigInteger(big.NewInt(int64(r.ExpiresAt))),
		stackitem.NewByteArray(caseID[:]),
	}))

	return stackitem.NewBool(true)
}

func (l *Lex) liftRestriction(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	if err := l.checkJudicialAuthority(ic); err != nil {
		panic(err)
	}

	subject := toUint160(args[0])
	rightID := toUint64(args[1])
	reason := toString(args[2])

	if len(reason) > maxReasonLength {
		panic(ErrReasonTooLong)
	}

	r := l.getRestrictionInternal(ic.DAO, subject, rightID)
	if r == nil {
		panic(ErrRestrictionNotFound)
	}

	l.deleteRestriction(ic.DAO, subject, rightID)
	l.removeSubjectRestrictedRight(ic.DAO, subject, rightID)

	ic.AddNotification(l.Hash, RestrictionLiftedEvent, stackitem.NewArray([]stackitem.Item{
		stackitem.NewByteArray(subject.BytesBE()),
		stackitem.NewBigInteger(big.NewInt(int64(rightID))),
		stackitem.NewByteArray([]byte(reason)),
	}))

	return stackitem.NewBool(true)
}

// ==================== Cross-Contract Access Methods ====================

// HasRightInternal checks if a subject has a specific right (for cross-native access).
// Returns true if the subject has an active Vita and the right is not restricted.
func (l *Lex) HasRightInternal(d *dao.Simple, subject util.Uint160, rightID uint64, blockHeight uint32) bool {
	// Check if it's a constitutional right
	if !state.IsConstitutionalRight(rightID) {
		return false
	}

	// Must have active Vita to have rights
	if l.Vita != nil {
		token, err := l.Vita.GetTokenByOwner(d, subject)
		if err != nil || token == nil || token.Status != state.TokenStatusActive {
			// Check asylum (humanitarian override)
			if l.Federation != nil && l.Federation.HasAsylum(d, subject) {
				// Asylum seekers retain rights even without local active Vita
			} else {
				return false
			}
		}
	}

	// Check if right is restricted
	return !l.IsRestrictedInternal(d, subject, rightID, blockHeight)
}

// IsRestrictedInternal checks if a subject's right is restricted (for cross-native access).
func (l *Lex) IsRestrictedInternal(d *dao.Simple, subject util.Uint160, rightID uint64, blockHeight uint32) bool {
	r := l.getRestrictionInternal(d, subject, rightID)
	if r == nil {
		return false
	}
	// Check if expired
	return !r.IsExpired(blockHeight)
}

// GetConstitutionalRightInternal returns a constitutional right (for cross-native access).
func (l *Lex) GetConstitutionalRightInternal(rightID uint64) *state.ConstitutionalRight {
	return state.GetConstitutionalRight(rightID)
}

// CheckPropertyRight checks if subject has property rights (for VTS integration).
func (l *Lex) CheckPropertyRight(d *dao.Simple, subject util.Uint160, blockHeight uint32) bool {
	return l.HasRightInternal(d, subject, state.RightProperty, blockHeight)
}

// CheckMovementRight checks if subject has movement rights (for Federation integration).
func (l *Lex) CheckMovementRight(d *dao.Simple, subject util.Uint160, blockHeight uint32) bool {
	return l.HasRightInternal(d, subject, state.RightMovement, blockHeight)
}

// CheckLibertyRight checks if subject has liberty rights.
func (l *Lex) CheckLibertyRight(d *dao.Simple, subject util.Uint160, blockHeight uint32) bool {
	return l.HasRightInternal(d, subject, state.RightLiberty, blockHeight)
}

// RatifyAmendmentInternal creates a new law from a passed Eligere proposal.
// This is called by the Eligere contract when a law amendment proposal passes.
// Returns the new law ID.
func (l *Lex) RatifyAmendmentInternal(ic *interop.Context, proposalID uint64, contentHash util.Uint256, category state.LawCategory, jurisdiction uint32) uint64 {
	// Category validation
	if category < state.LawCategoryFederal || category > state.LawCategoryAdministrative {
		panic(ErrInvalidLawCategory)
	}
	// Cannot create constitutional laws via amendment - they are immutable
	if category == state.LawCategoryConstitutional {
		panic(ErrCannotModifyConst)
	}

	// Create law from ratified proposal
	lawID := l.getLawCounter(ic.DAO)
	name := fmt.Sprintf("Amendment_%d", proposalID)

	law := &state.Law{
		ID:           lawID,
		Name:         name,
		ContentHash:  contentHash,
		Category:     category,
		Jurisdiction: jurisdiction,
		ParentID:     0,
		EffectiveAt:  ic.Block.Index,
		ExpiresAt:    0, // Perpetual by default
		EnactedAt:    ic.Block.Index,
		EnactedBy:    ic.VM.GetCallingScriptHash(),
		Status:       state.LawStatusActive,
		SupersededBy: 0,
		RequiresVita: true,
		Enforcement:  state.EnforcementAutomatic,
	}

	l.putLaw(ic.DAO, law)
	l.putLawNameIndex(ic.DAO, name, lawID)
	l.putLawCounter(ic.DAO, lawID+1)

	ic.AddNotification(l.Hash, LawEnactedEvent, stackitem.NewArray([]stackitem.Item{
		stackitem.NewBigInteger(big.NewInt(int64(lawID))),
		stackitem.NewByteArray([]byte(name)),
		stackitem.NewBigInteger(big.NewInt(int64(category))),
		stackitem.NewByteArray(law.EnactedBy.BytesBE()),
	}))

	return lawID
}
