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

// Pons ("bridge" in Latin) represents the Inter-Government Bridge Protocol
// native contract. It manages:
// - Bilateral agreements between sovereign chains
// - Cross-border verification requests
// - International VTS settlement
// - Education and healthcare credential portability
type Pons struct {
	interop.ContractMD
	Tutus ITutus
	Vita         IVita
	Federation   *Federation
	RoleRegistry *RoleRegistry
	VTS          *VTS
	Scire        *Scire
	Salus        *Salus
}

// Storage key prefixes for Pons.
const (
	ponsPrefixConfig           byte = 0x01 // -> PonsConfig
	ponsPrefixAgreement        byte = 0x10 // agreementID -> BilateralAgreement
	ponsPrefixAgreementByChain byte = 0x11 // chainID + agreementID -> exists
	ponsPrefixAgreementCounter byte = 0x1F // -> next agreementID
	ponsPrefixVerification     byte = 0x20 // requestID -> VerificationRequest
	ponsPrefixVerifBySubject   byte = 0x21 // subject + requestID -> exists
	ponsPrefixVerifCounter     byte = 0x2F // -> next verificationID
	ponsPrefixSettlement       byte = 0x30 // settlementID -> SettlementRequest
	ponsPrefixSettlByChain     byte = 0x31 // chainID + settlementID -> exists
	ponsPrefixSettlCounter     byte = 0x3F // -> next settlementID
	ponsPrefixCredential       byte = 0x40 // credentialID -> CredentialShare
	ponsPrefixCredByOwner      byte = 0x41 // owner + credentialID -> exists
	ponsPrefixCredCounter      byte = 0x4F // -> next credentialID
)

// Default configuration values.
const (
	defaultLocalChainID          uint32 = 1
	defaultVerificationTimeout   uint32 = 8640   // ~1 day at 10s blocks
	defaultSettlementTimeout     uint32 = 86400  // ~10 days
	defaultMaxPendingRequests    uint64 = 10000
	defaultCredentialShareExpiry uint32 = 315360 // ~1 year
)

// Event names for Pons.
const (
	AgreementCreatedEvent     = "AgreementCreated"
	AgreementUpdatedEvent     = "AgreementUpdated"
	AgreementTerminatedEvent  = "AgreementTerminated"
	VerificationRequestedEvent = "VerificationRequested"
	VerificationRespondedEvent = "VerificationResponded"
	SettlementRequestedEvent  = "SettlementRequested"
	SettlementCompletedEvent  = "SettlementCompleted"
	CredentialSharedEvent     = "CredentialShared"
	CredentialRevokedEvent    = "CredentialRevoked"
)

// Various errors for Pons.
var (
	ErrNoAgreement           = errors.New("no active agreement with target chain")
	ErrAgreementExists       = errors.New("agreement already exists")
	ErrAgreementNotFound     = errors.New("agreement not found")
	ErrInvalidAgreementType  = errors.New("invalid agreement type")
	ErrAgreementNotActive    = errors.New("agreement not active")
	ErrVerificationNotFound  = errors.New("verification request not found")
	ErrVerificationExpired   = errors.New("verification request expired")
	ErrSettlementNotFound    = errors.New("settlement request not found")
	ErrSettlementExpired     = errors.New("settlement request expired")
	ErrCredentialNotFound    = errors.New("credential share not found")
	ErrCredentialExpired     = errors.New("credential share expired")
	ErrCredentialRevoked     = errors.New("credential share revoked")
	ErrMaxRequestsReached    = errors.New("maximum pending requests reached")
	ErrNotCredentialOwner    = errors.New("not credential owner")
	ErrAgreementTypeNotAllowed = errors.New("agreement type not allowed for this operation")
)

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

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

	// getConfig method
	desc := NewDescriptor("getConfig", smartcontract.ArrayType)
	md := NewMethodAndPrice(p.getConfig, 1<<15, callflag.ReadStates)
	p.AddMethod(md, desc)

	// setLocalChainID method (committee only)
	desc = NewDescriptor("setLocalChainID", smartcontract.BoolType,
		manifest.NewParameter("chainID", smartcontract.IntegerType))
	md = NewMethodAndPrice(p.setLocalChainID, 1<<16, callflag.States|callflag.AllowNotify)
	p.AddMethod(md, desc)

	// --- Agreement Management ---

	// createAgreement method (committee only)
	desc = NewDescriptor("createAgreement", smartcontract.IntegerType,
		manifest.NewParameter("remoteChainID", smartcontract.IntegerType),
		manifest.NewParameter("agreementType", smartcontract.IntegerType),
		manifest.NewParameter("termsHash", smartcontract.Hash256Type),
		manifest.NewParameter("expirationHeight", smartcontract.IntegerType))
	md = NewMethodAndPrice(p.createAgreement, 1<<17, callflag.States|callflag.AllowNotify)
	p.AddMethod(md, desc)

	// updateAgreementStatus method (committee only)
	desc = NewDescriptor("updateAgreementStatus", smartcontract.BoolType,
		manifest.NewParameter("agreementID", smartcontract.IntegerType),
		manifest.NewParameter("newStatus", smartcontract.IntegerType))
	md = NewMethodAndPrice(p.updateAgreementStatus, 1<<16, callflag.States|callflag.AllowNotify)
	p.AddMethod(md, desc)

	// getAgreement method
	desc = NewDescriptor("getAgreement", smartcontract.ArrayType,
		manifest.NewParameter("agreementID", smartcontract.IntegerType))
	md = NewMethodAndPrice(p.getAgreement, 1<<15, callflag.ReadStates)
	p.AddMethod(md, desc)

	// hasActiveAgreement method
	desc = NewDescriptor("hasActiveAgreement", smartcontract.BoolType,
		manifest.NewParameter("remoteChainID", smartcontract.IntegerType),
		manifest.NewParameter("agreementType", smartcontract.IntegerType))
	md = NewMethodAndPrice(p.hasActiveAgreement, 1<<15, callflag.ReadStates)
	p.AddMethod(md, desc)

	// --- Verification Requests ---

	// requestVerification method
	desc = NewDescriptor("requestVerification", smartcontract.IntegerType,
		manifest.NewParameter("targetChainID", smartcontract.IntegerType),
		manifest.NewParameter("subject", smartcontract.Hash160Type),
		manifest.NewParameter("verificationType", smartcontract.IntegerType),
		manifest.NewParameter("dataHash", smartcontract.Hash256Type))
	md = NewMethodAndPrice(p.requestVerification, 1<<17, callflag.States|callflag.AllowNotify)
	p.AddMethod(md, desc)

	// respondVerification method (committee only - represents response from other chain)
	desc = NewDescriptor("respondVerification", smartcontract.BoolType,
		manifest.NewParameter("requestID", smartcontract.IntegerType),
		manifest.NewParameter("approved", smartcontract.BoolType),
		manifest.NewParameter("responseHash", smartcontract.Hash256Type))
	md = NewMethodAndPrice(p.respondVerification, 1<<16, callflag.States|callflag.AllowNotify)
	p.AddMethod(md, desc)

	// getVerificationRequest method
	desc = NewDescriptor("getVerificationRequest", smartcontract.ArrayType,
		manifest.NewParameter("requestID", smartcontract.IntegerType))
	md = NewMethodAndPrice(p.getVerificationRequest, 1<<15, callflag.ReadStates)
	p.AddMethod(md, desc)

	// --- Settlement Requests ---

	// requestSettlement method
	desc = NewDescriptor("requestSettlement", smartcontract.IntegerType,
		manifest.NewParameter("toChainID", smartcontract.IntegerType),
		manifest.NewParameter("receiver", smartcontract.Hash160Type),
		manifest.NewParameter("amount", smartcontract.IntegerType),
		manifest.NewParameter("reference", smartcontract.StringType))
	md = NewMethodAndPrice(p.requestSettlement, 1<<17, callflag.States|callflag.AllowNotify)
	p.AddMethod(md, desc)

	// completeSettlement method (committee only - represents confirmation from other chain)
	desc = NewDescriptor("completeSettlement", smartcontract.BoolType,
		manifest.NewParameter("settlementID", smartcontract.IntegerType),
		manifest.NewParameter("txHash", smartcontract.Hash256Type))
	md = NewMethodAndPrice(p.completeSettlement, 1<<16, callflag.States|callflag.AllowNotify)
	p.AddMethod(md, desc)

	// cancelSettlement method (sender or committee)
	desc = NewDescriptor("cancelSettlement", smartcontract.BoolType,
		manifest.NewParameter("settlementID", smartcontract.IntegerType))
	md = NewMethodAndPrice(p.cancelSettlement, 1<<16, callflag.States|callflag.AllowNotify)
	p.AddMethod(md, desc)

	// getSettlementRequest method
	desc = NewDescriptor("getSettlementRequest", smartcontract.ArrayType,
		manifest.NewParameter("settlementID", smartcontract.IntegerType))
	md = NewMethodAndPrice(p.getSettlementRequest, 1<<15, callflag.ReadStates)
	p.AddMethod(md, desc)

	// --- Credential Sharing ---

	// shareCredential method
	desc = NewDescriptor("shareCredential", smartcontract.IntegerType,
		manifest.NewParameter("targetChainID", smartcontract.IntegerType),
		manifest.NewParameter("credentialType", smartcontract.IntegerType),
		manifest.NewParameter("credentialID", smartcontract.IntegerType),
		manifest.NewParameter("contentHash", smartcontract.Hash256Type),
		manifest.NewParameter("validUntil", smartcontract.IntegerType))
	md = NewMethodAndPrice(p.shareCredential, 1<<17, callflag.States|callflag.AllowNotify)
	p.AddMethod(md, desc)

	// revokeCredentialShare method
	desc = NewDescriptor("revokeCredentialShare", smartcontract.BoolType,
		manifest.NewParameter("shareID", smartcontract.IntegerType))
	md = NewMethodAndPrice(p.revokeCredentialShare, 1<<16, callflag.States|callflag.AllowNotify)
	p.AddMethod(md, desc)

	// getCredentialShare method
	desc = NewDescriptor("getCredentialShare", smartcontract.ArrayType,
		manifest.NewParameter("shareID", smartcontract.IntegerType))
	md = NewMethodAndPrice(p.getCredentialShare, 1<<15, callflag.ReadStates)
	p.AddMethod(md, desc)

	// verifyCredentialShare method
	desc = NewDescriptor("verifyCredentialShare", smartcontract.BoolType,
		manifest.NewParameter("shareID", smartcontract.IntegerType))
	md = NewMethodAndPrice(p.verifyCredentialShare, 1<<15, callflag.ReadStates)
	p.AddMethod(md, desc)

	// --- Counter Query Methods ---

	// getAgreementCount method
	desc = NewDescriptor("getAgreementCount", smartcontract.IntegerType)
	md = NewMethodAndPrice(p.getAgreementCount, 1<<15, callflag.ReadStates)
	p.AddMethod(md, desc)

	// getVerificationCount method
	desc = NewDescriptor("getVerificationCount", smartcontract.IntegerType)
	md = NewMethodAndPrice(p.getVerificationCount, 1<<15, callflag.ReadStates)
	p.AddMethod(md, desc)

	// getSettlementCount method
	desc = NewDescriptor("getSettlementCount", smartcontract.IntegerType)
	md = NewMethodAndPrice(p.getSettlementCount, 1<<15, callflag.ReadStates)
	p.AddMethod(md, desc)

	// getCredentialShareCount method
	desc = NewDescriptor("getCredentialShareCount", smartcontract.IntegerType)
	md = NewMethodAndPrice(p.getCredentialShareCount, 1<<15, callflag.ReadStates)
	p.AddMethod(md, desc)

	// --- Events ---

	eDesc := NewEventDescriptor(AgreementCreatedEvent,
		manifest.NewParameter("agreementID", smartcontract.IntegerType),
		manifest.NewParameter("remoteChainID", smartcontract.IntegerType),
		manifest.NewParameter("agreementType", smartcontract.IntegerType))
	p.AddEvent(NewEvent(eDesc))

	eDesc = NewEventDescriptor(AgreementUpdatedEvent,
		manifest.NewParameter("agreementID", smartcontract.IntegerType),
		manifest.NewParameter("oldStatus", smartcontract.IntegerType),
		manifest.NewParameter("newStatus", smartcontract.IntegerType))
	p.AddEvent(NewEvent(eDesc))

	eDesc = NewEventDescriptor(AgreementTerminatedEvent,
		manifest.NewParameter("agreementID", smartcontract.IntegerType),
		manifest.NewParameter("remoteChainID", smartcontract.IntegerType))
	p.AddEvent(NewEvent(eDesc))

	eDesc = NewEventDescriptor(VerificationRequestedEvent,
		manifest.NewParameter("requestID", smartcontract.IntegerType),
		manifest.NewParameter("targetChainID", smartcontract.IntegerType),
		manifest.NewParameter("subject", smartcontract.Hash160Type),
		manifest.NewParameter("verificationType", smartcontract.IntegerType))
	p.AddEvent(NewEvent(eDesc))

	eDesc = NewEventDescriptor(VerificationRespondedEvent,
		manifest.NewParameter("requestID", smartcontract.IntegerType),
		manifest.NewParameter("approved", smartcontract.BoolType))
	p.AddEvent(NewEvent(eDesc))

	eDesc = NewEventDescriptor(SettlementRequestedEvent,
		manifest.NewParameter("settlementID", smartcontract.IntegerType),
		manifest.NewParameter("toChainID", smartcontract.IntegerType),
		manifest.NewParameter("receiver", smartcontract.Hash160Type),
		manifest.NewParameter("amount", smartcontract.IntegerType))
	p.AddEvent(NewEvent(eDesc))

	eDesc = NewEventDescriptor(SettlementCompletedEvent,
		manifest.NewParameter("settlementID", smartcontract.IntegerType),
		manifest.NewParameter("txHash", smartcontract.Hash256Type))
	p.AddEvent(NewEvent(eDesc))

	eDesc = NewEventDescriptor(CredentialSharedEvent,
		manifest.NewParameter("shareID", smartcontract.IntegerType),
		manifest.NewParameter("owner", smartcontract.Hash160Type),
		manifest.NewParameter("targetChainID", smartcontract.IntegerType),
		manifest.NewParameter("credentialType", smartcontract.IntegerType))
	p.AddEvent(NewEvent(eDesc))

	eDesc = NewEventDescriptor(CredentialRevokedEvent,
		manifest.NewParameter("shareID", smartcontract.IntegerType),
		manifest.NewParameter("owner", smartcontract.Hash160Type))
	p.AddEvent(NewEvent(eDesc))

	return p
}

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

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

	// Initialize default config
	cfg := state.PonsConfig{
		LocalChainID:          defaultLocalChainID,
		VerificationTimeout:   defaultVerificationTimeout,
		SettlementTimeout:     defaultSettlementTimeout,
		MaxPendingRequests:    defaultMaxPendingRequests,
		CredentialShareExpiry: defaultCredentialShareExpiry,
	}
	p.setConfigInternal(ic.DAO, &cfg)

	return nil
}

// InitializeCache fills native Pons cache from DAO on node restart.
func (p *Pons) InitializeCache(_ interop.IsHardforkEnabled, blockHeight uint32, d *dao.Simple) error {
	return nil
}

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

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

// ActiveIn returns the hardfork this contract activates in (nil = always active).
func (p *Pons) ActiveIn() *config.Hardfork {
	return nil
}

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

func makePonsConfigKey() []byte {
	return []byte{ponsPrefixConfig}
}

func makePonsAgreementKey(agreementID uint64) []byte {
	key := make([]byte, 9)
	key[0] = ponsPrefixAgreement
	binary.BigEndian.PutUint64(key[1:], agreementID)
	return key
}

func makePonsAgreementByChainKey(chainID uint32, agreementID uint64) []byte {
	key := make([]byte, 13)
	key[0] = ponsPrefixAgreementByChain
	binary.BigEndian.PutUint32(key[1:], chainID)
	binary.BigEndian.PutUint64(key[5:], agreementID)
	return key
}

func makePonsAgreementCounterKey() []byte {
	return []byte{ponsPrefixAgreementCounter}
}

func makePonsVerificationKey(requestID uint64) []byte {
	key := make([]byte, 9)
	key[0] = ponsPrefixVerification
	binary.BigEndian.PutUint64(key[1:], requestID)
	return key
}

func makePonsVerifBySubjectKey(subject util.Uint160, requestID uint64) []byte {
	key := make([]byte, 1+util.Uint160Size+8)
	key[0] = ponsPrefixVerifBySubject
	copy(key[1:], subject.BytesBE())
	binary.BigEndian.PutUint64(key[1+util.Uint160Size:], requestID)
	return key
}

func makePonsVerificationCounterKey() []byte {
	return []byte{ponsPrefixVerifCounter}
}

func makePonsSettlementKey(settlementID uint64) []byte {
	key := make([]byte, 9)
	key[0] = ponsPrefixSettlement
	binary.BigEndian.PutUint64(key[1:], settlementID)
	return key
}

func makePonsSettlByChainKey(chainID uint32, settlementID uint64) []byte {
	key := make([]byte, 13)
	key[0] = ponsPrefixSettlByChain
	binary.BigEndian.PutUint32(key[1:], chainID)
	binary.BigEndian.PutUint64(key[5:], settlementID)
	return key
}

func makePonsSettlementCounterKey() []byte {
	return []byte{ponsPrefixSettlCounter}
}

func makePonsCredentialKey(credentialID uint64) []byte {
	key := make([]byte, 9)
	key[0] = ponsPrefixCredential
	binary.BigEndian.PutUint64(key[1:], credentialID)
	return key
}

func makePonsCredByOwnerKey(owner util.Uint160, credentialID uint64) []byte {
	key := make([]byte, 1+util.Uint160Size+8)
	key[0] = ponsPrefixCredByOwner
	copy(key[1:], owner.BytesBE())
	binary.BigEndian.PutUint64(key[1+util.Uint160Size:], credentialID)
	return key
}

func makePonsCredentialCounterKey() []byte {
	return []byte{ponsPrefixCredCounter}
}

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

func (p *Pons) getConfigInternal(d *dao.Simple) *state.PonsConfig {
	si := d.GetStorageItem(p.ID, makePonsConfigKey())
	if si == nil {
		return &state.PonsConfig{
			LocalChainID:          defaultLocalChainID,
			VerificationTimeout:   defaultVerificationTimeout,
			SettlementTimeout:     defaultSettlementTimeout,
			MaxPendingRequests:    defaultMaxPendingRequests,
			CredentialShareExpiry: defaultCredentialShareExpiry,
		}
	}
	// Decode config: chainID(4) + verifTimeout(4) + settlTimeout(4) + maxReq(8) + credExpiry(4) = 24 bytes
	if len(si) < 24 {
		return &state.PonsConfig{
			LocalChainID:          defaultLocalChainID,
			VerificationTimeout:   defaultVerificationTimeout,
			SettlementTimeout:     defaultSettlementTimeout,
			MaxPendingRequests:    defaultMaxPendingRequests,
			CredentialShareExpiry: defaultCredentialShareExpiry,
		}
	}
	return &state.PonsConfig{
		LocalChainID:          binary.BigEndian.Uint32(si[0:4]),
		VerificationTimeout:   binary.BigEndian.Uint32(si[4:8]),
		SettlementTimeout:     binary.BigEndian.Uint32(si[8:12]),
		MaxPendingRequests:    binary.BigEndian.Uint64(si[12:20]),
		CredentialShareExpiry: binary.BigEndian.Uint32(si[20:24]),
	}
}

func (p *Pons) setConfigInternal(d *dao.Simple, cfg *state.PonsConfig) {
	buf := make([]byte, 24)
	binary.BigEndian.PutUint32(buf[0:4], cfg.LocalChainID)
	binary.BigEndian.PutUint32(buf[4:8], cfg.VerificationTimeout)
	binary.BigEndian.PutUint32(buf[8:12], cfg.SettlementTimeout)
	binary.BigEndian.PutUint64(buf[12:20], cfg.MaxPendingRequests)
	binary.BigEndian.PutUint32(buf[20:24], cfg.CredentialShareExpiry)
	d.PutStorageItem(p.ID, makePonsConfigKey(), buf)
}

func (p *Pons) getCounterInternal(d *dao.Simple, key []byte) uint64 {
	si := d.GetStorageItem(p.ID, key)
	if si == nil || len(si) < 8 {
		return 0
	}
	return binary.BigEndian.Uint64(si)
}

func (p *Pons) incrementCounterInternal(d *dao.Simple, key []byte) uint64 {
	current := p.getCounterInternal(d, key)
	next := current + 1
	buf := make([]byte, 8)
	binary.BigEndian.PutUint64(buf, next)
	d.PutStorageItem(p.ID, key, buf)
	return next
}

// Agreement storage format:
// localChainID(4) + remoteChainID(4) + agreementType(1) + status(1) + terms(32) +
// effectiveDate(4) + expirationDate(4) + createdAt(4) + updatedAt(4) = 58 bytes
func (p *Pons) getAgreementInternal(d *dao.Simple, agreementID uint64) (*state.BilateralAgreement, bool) {
	si := d.GetStorageItem(p.ID, makePonsAgreementKey(agreementID))
	if si == nil || len(si) < 58 {
		return nil, false
	}

	var terms util.Uint256
	copy(terms[:], si[10:42])

	return &state.BilateralAgreement{
		ID:             agreementID,
		LocalChainID:   binary.BigEndian.Uint32(si[0:4]),
		RemoteChainID:  binary.BigEndian.Uint32(si[4:8]),
		AgreementType:  state.AgreementType(si[8]),
		Status:         state.AgreementStatus(si[9]),
		Terms:          terms,
		EffectiveDate:  binary.BigEndian.Uint32(si[42:46]),
		ExpirationDate: binary.BigEndian.Uint32(si[46:50]),
		CreatedAt:      binary.BigEndian.Uint32(si[50:54]),
		UpdatedAt:      binary.BigEndian.Uint32(si[54:58]),
	}, true
}

func (p *Pons) setAgreementInternal(d *dao.Simple, agr *state.BilateralAgreement) {
	buf := make([]byte, 58)
	binary.BigEndian.PutUint32(buf[0:4], agr.LocalChainID)
	binary.BigEndian.PutUint32(buf[4:8], agr.RemoteChainID)
	buf[8] = byte(agr.AgreementType)
	buf[9] = byte(agr.Status)
	copy(buf[10:42], agr.Terms[:])
	binary.BigEndian.PutUint32(buf[42:46], agr.EffectiveDate)
	binary.BigEndian.PutUint32(buf[46:50], agr.ExpirationDate)
	binary.BigEndian.PutUint32(buf[50:54], agr.CreatedAt)
	binary.BigEndian.PutUint32(buf[54:58], agr.UpdatedAt)
	d.PutStorageItem(p.ID, makePonsAgreementKey(agr.ID), buf)
	// Index by chain
	d.PutStorageItem(p.ID, makePonsAgreementByChainKey(agr.RemoteChainID, agr.ID), []byte{1})
}

// Verification storage format:
// requestingChain(4) + targetChain(4) + subject(20) + verificationType(1) + dataHash(32) +
// status(1) + responseHash(32) + requester(20) + createdAt(4) + expiresAt(4) + respondedAt(4) = 126 bytes
func (p *Pons) getVerificationInternal(d *dao.Simple, requestID uint64) (*state.VerificationRequest, bool) {
	si := d.GetStorageItem(p.ID, makePonsVerificationKey(requestID))
	if si == nil || len(si) < 126 {
		return nil, false
	}

	var subject util.Uint160
	copy(subject[:], si[8:28])
	var dataHash util.Uint256
	copy(dataHash[:], si[29:61])
	var responseHash util.Uint256
	copy(responseHash[:], si[62:94])
	var requester util.Uint160
	copy(requester[:], si[94:114])

	return &state.VerificationRequest{
		ID:               requestID,
		RequestingChain:  binary.BigEndian.Uint32(si[0:4]),
		TargetChain:      binary.BigEndian.Uint32(si[4:8]),
		Subject:          subject,
		VerificationType: state.VerificationType(si[28]),
		DataHash:         dataHash,
		Status:           state.VerificationStatus(si[61]),
		ResponseHash:     responseHash,
		Requester:        requester,
		CreatedAt:        binary.BigEndian.Uint32(si[114:118]),
		ExpiresAt:        binary.BigEndian.Uint32(si[118:122]),
		RespondedAt:      binary.BigEndian.Uint32(si[122:126]),
	}, true
}

func (p *Pons) setVerificationInternal(d *dao.Simple, vr *state.VerificationRequest) {
	buf := make([]byte, 126)
	binary.BigEndian.PutUint32(buf[0:4], vr.RequestingChain)
	binary.BigEndian.PutUint32(buf[4:8], vr.TargetChain)
	copy(buf[8:28], vr.Subject[:])
	buf[28] = byte(vr.VerificationType)
	copy(buf[29:61], vr.DataHash[:])
	buf[61] = byte(vr.Status)
	copy(buf[62:94], vr.ResponseHash[:])
	copy(buf[94:114], vr.Requester[:])
	binary.BigEndian.PutUint32(buf[114:118], vr.CreatedAt)
	binary.BigEndian.PutUint32(buf[118:122], vr.ExpiresAt)
	binary.BigEndian.PutUint32(buf[122:126], vr.RespondedAt)
	d.PutStorageItem(p.ID, makePonsVerificationKey(vr.ID), buf)
	// Index by subject
	d.PutStorageItem(p.ID, makePonsVerifBySubjectKey(vr.Subject, vr.ID), []byte{1})
}

// Settlement storage format:
// fromChain(4) + toChain(4) + sender(20) + receiver(20) + amount(8) + status(1) +
// createdAt(4) + settledAt(4) + txHash(32) + refLen(2) + reference(var) = 99 + ref bytes
func (p *Pons) getSettlementInternal(d *dao.Simple, settlementID uint64) (*state.SettlementRequest, bool) {
	si := d.GetStorageItem(p.ID, makePonsSettlementKey(settlementID))
	if si == nil || len(si) < 99 {
		return nil, false
	}

	var sender util.Uint160
	copy(sender[:], si[8:28])
	var receiver util.Uint160
	copy(receiver[:], si[28:48])
	var txHash util.Uint256
	copy(txHash[:], si[65:97])

	refLen := binary.BigEndian.Uint16(si[97:99])
	var reference string
	if len(si) >= 99+int(refLen) {
		reference = string(si[99 : 99+refLen])
	}

	return &state.SettlementRequest{
		ID:        settlementID,
		FromChain: binary.BigEndian.Uint32(si[0:4]),
		ToChain:   binary.BigEndian.Uint32(si[4:8]),
		Sender:    sender,
		Receiver:  receiver,
		Amount:    binary.BigEndian.Uint64(si[48:56]),
		Status:    state.SettlementStatus(si[56]),
		CreatedAt: binary.BigEndian.Uint32(si[57:61]),
		SettledAt: binary.BigEndian.Uint32(si[61:65]),
		TxHash:    txHash,
		Reference: reference,
	}, true
}

func (p *Pons) setSettlementInternal(d *dao.Simple, sr *state.SettlementRequest) {
	refBytes := []byte(sr.Reference)
	buf := make([]byte, 99+len(refBytes))
	binary.BigEndian.PutUint32(buf[0:4], sr.FromChain)
	binary.BigEndian.PutUint32(buf[4:8], sr.ToChain)
	copy(buf[8:28], sr.Sender[:])
	copy(buf[28:48], sr.Receiver[:])
	binary.BigEndian.PutUint64(buf[48:56], sr.Amount)
	buf[56] = byte(sr.Status)
	binary.BigEndian.PutUint32(buf[57:61], sr.CreatedAt)
	binary.BigEndian.PutUint32(buf[61:65], sr.SettledAt)
	copy(buf[65:97], sr.TxHash[:])
	binary.BigEndian.PutUint16(buf[97:99], uint16(len(refBytes)))
	copy(buf[99:], refBytes)
	d.PutStorageItem(p.ID, makePonsSettlementKey(sr.ID), buf)
	// Index by chain
	d.PutStorageItem(p.ID, makePonsSettlByChainKey(sr.ToChain, sr.ID), []byte{1})
}

// CredentialShare storage format:
// sourceChain(4) + targetChain(4) + owner(20) + credentialType(1) + credentialID(8) +
// contentHash(32) + validUntil(4) + createdAt(4) + isRevoked(1) = 78 bytes
func (p *Pons) getCredentialShareInternal(d *dao.Simple, shareID uint64) (*state.CredentialShare, bool) {
	si := d.GetStorageItem(p.ID, makePonsCredentialKey(shareID))
	if si == nil || len(si) < 78 {
		return nil, false
	}

	var owner util.Uint160
	copy(owner[:], si[8:28])
	var contentHash util.Uint256
	copy(contentHash[:], si[37:69])

	return &state.CredentialShare{
		ID:             shareID,
		SourceChain:    binary.BigEndian.Uint32(si[0:4]),
		TargetChain:    binary.BigEndian.Uint32(si[4:8]),
		Owner:          owner,
		CredentialType: state.VerificationType(si[28]),
		CredentialID:   binary.BigEndian.Uint64(si[29:37]),
		ContentHash:    contentHash,
		ValidUntil:     binary.BigEndian.Uint32(si[69:73]),
		CreatedAt:      binary.BigEndian.Uint32(si[73:77]),
		IsRevoked:      si[77] != 0,
	}, true
}

func (p *Pons) setCredentialShareInternal(d *dao.Simple, cs *state.CredentialShare) {
	buf := make([]byte, 78)
	binary.BigEndian.PutUint32(buf[0:4], cs.SourceChain)
	binary.BigEndian.PutUint32(buf[4:8], cs.TargetChain)
	copy(buf[8:28], cs.Owner[:])
	buf[28] = byte(cs.CredentialType)
	binary.BigEndian.PutUint64(buf[29:37], cs.CredentialID)
	copy(buf[37:69], cs.ContentHash[:])
	binary.BigEndian.PutUint32(buf[69:73], cs.ValidUntil)
	binary.BigEndian.PutUint32(buf[73:77], cs.CreatedAt)
	if cs.IsRevoked {
		buf[77] = 1
	}
	d.PutStorageItem(p.ID, makePonsCredentialKey(cs.ID), buf)
	// Index by owner
	d.PutStorageItem(p.ID, makePonsCredByOwnerKey(cs.Owner, cs.ID), []byte{1})
}

// hasActiveAgreementInternal checks if there's an active agreement with the target chain
// for the specified agreement type.
func (p *Pons) hasActiveAgreementInternal(d *dao.Simple, remoteChainID uint32, agreementType state.AgreementType, blockHeight uint32) bool {
	count := p.getCounterInternal(d, makePonsAgreementCounterKey())
	for i := uint64(1); i <= count; i++ {
		agr, exists := p.getAgreementInternal(d, i)
		if !exists {
			continue
		}
		if agr.RemoteChainID != remoteChainID {
			continue
		}
		if agr.Status != state.AgreementActive {
			continue
		}
		// Check expiration
		if agr.ExpirationDate > 0 && agr.ExpirationDate < blockHeight {
			continue
		}
		// Check if agreement type matches or is comprehensive
		if agr.AgreementType == agreementType || agr.AgreementType == state.AgreementTypeComprehensive {
			return true
		}
	}
	return false
}

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

func (p *Pons) getConfig(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
	cfg := p.getConfigInternal(ic.DAO)
	return stackitem.NewArray([]stackitem.Item{
		stackitem.NewBigInteger(big.NewInt(int64(cfg.LocalChainID))),
		stackitem.NewBigInteger(big.NewInt(int64(cfg.VerificationTimeout))),
		stackitem.NewBigInteger(big.NewInt(int64(cfg.SettlementTimeout))),
		stackitem.NewBigInteger(new(big.Int).SetUint64(cfg.MaxPendingRequests)),
		stackitem.NewBigInteger(big.NewInt(int64(cfg.CredentialShareExpiry))),
	})
}

func (p *Pons) setLocalChainID(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	chainID := uint32(toBigInt(args[0]).Int64())

	if !p.Tutus.CheckCommittee(ic) {
		panic("only committee can set chain ID")
	}

	cfg := p.getConfigInternal(ic.DAO)
	cfg.LocalChainID = chainID
	p.setConfigInternal(ic.DAO, cfg)

	return stackitem.NewBool(true)
}

func (p *Pons) createAgreement(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	remoteChainID := uint32(toBigInt(args[0]).Int64())
	agreementType := state.AgreementType(toBigInt(args[1]).Int64())
	termsHashBytes, err := args[2].TryBytes()
	if err != nil {
		panic(err)
	}
	termsHash, err := util.Uint256DecodeBytesBE(termsHashBytes)
	if err != nil {
		panic(err)
	}
	expirationHeight := uint32(toBigInt(args[3]).Int64())

	if !p.Tutus.CheckCommittee(ic) {
		panic("only committee can create agreements")
	}

	if agreementType > state.AgreementTypeComprehensive {
		panic(ErrInvalidAgreementType)
	}

	cfg := p.getConfigInternal(ic.DAO)

	// Create agreement
	agreementID := p.incrementCounterInternal(ic.DAO, makePonsAgreementCounterKey())
	agr := &state.BilateralAgreement{
		ID:             agreementID,
		LocalChainID:   cfg.LocalChainID,
		RemoteChainID:  remoteChainID,
		AgreementType:  agreementType,
		Status:         state.AgreementPending,
		Terms:          termsHash,
		EffectiveDate:  0, // Set when activated
		ExpirationDate: expirationHeight,
		CreatedAt:      ic.Block.Index,
		UpdatedAt:      ic.Block.Index,
	}
	p.setAgreementInternal(ic.DAO, agr)

	ic.AddNotification(p.Hash, AgreementCreatedEvent, stackitem.NewArray([]stackitem.Item{
		stackitem.NewBigInteger(new(big.Int).SetUint64(agreementID)),
		stackitem.NewBigInteger(big.NewInt(int64(remoteChainID))),
		stackitem.NewBigInteger(big.NewInt(int64(agreementType))),
	}))

	return stackitem.NewBigInteger(new(big.Int).SetUint64(agreementID))
}

func (p *Pons) updateAgreementStatus(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	agreementID := toBigInt(args[0]).Uint64()
	newStatus := state.AgreementStatus(toBigInt(args[1]).Int64())

	if !p.Tutus.CheckCommittee(ic) {
		panic("only committee can update agreement status")
	}

	agr, exists := p.getAgreementInternal(ic.DAO, agreementID)
	if !exists {
		panic(ErrAgreementNotFound)
	}

	oldStatus := agr.Status
	agr.Status = newStatus
	agr.UpdatedAt = ic.Block.Index

	// Set effective date when activating
	if newStatus == state.AgreementActive && oldStatus != state.AgreementActive {
		agr.EffectiveDate = ic.Block.Index
	}

	p.setAgreementInternal(ic.DAO, agr)

	ic.AddNotification(p.Hash, AgreementUpdatedEvent, stackitem.NewArray([]stackitem.Item{
		stackitem.NewBigInteger(new(big.Int).SetUint64(agreementID)),
		stackitem.NewBigInteger(big.NewInt(int64(oldStatus))),
		stackitem.NewBigInteger(big.NewInt(int64(newStatus))),
	}))

	return stackitem.NewBool(true)
}

func (p *Pons) getAgreement(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	agreementID := toBigInt(args[0]).Uint64()

	agr, exists := p.getAgreementInternal(ic.DAO, agreementID)
	if !exists {
		return stackitem.Null{}
	}

	return stackitem.NewArray([]stackitem.Item{
		stackitem.NewBigInteger(new(big.Int).SetUint64(agr.ID)),
		stackitem.NewBigInteger(big.NewInt(int64(agr.LocalChainID))),
		stackitem.NewBigInteger(big.NewInt(int64(agr.RemoteChainID))),
		stackitem.NewBigInteger(big.NewInt(int64(agr.AgreementType))),
		stackitem.NewBigInteger(big.NewInt(int64(agr.Status))),
		stackitem.NewByteArray(agr.Terms.BytesBE()),
		stackitem.NewBigInteger(big.NewInt(int64(agr.EffectiveDate))),
		stackitem.NewBigInteger(big.NewInt(int64(agr.ExpirationDate))),
		stackitem.NewBigInteger(big.NewInt(int64(agr.CreatedAt))),
		stackitem.NewBigInteger(big.NewInt(int64(agr.UpdatedAt))),
	})
}

func (p *Pons) hasActiveAgreement(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	remoteChainID := uint32(toBigInt(args[0]).Int64())
	agreementType := state.AgreementType(toBigInt(args[1]).Int64())

	has := p.hasActiveAgreementInternal(ic.DAO, remoteChainID, agreementType, ic.Block.Index)
	return stackitem.NewBool(has)
}

func (p *Pons) requestVerification(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	targetChainID := uint32(toBigInt(args[0]).Int64())
	subject := toUint160(args[1])
	verificationType := state.VerificationType(toBigInt(args[2]).Int64())
	dataHashBytes, err := args[3].TryBytes()
	if err != nil {
		panic(err)
	}
	dataHash, err := util.Uint256DecodeBytesBE(dataHashBytes)
	if err != nil {
		panic(err)
	}

	cfg := p.getConfigInternal(ic.DAO)

	// Check for active agreement
	agreementType := state.AgreementTypeIdentity
	switch verificationType {
	case state.VerificationTypeCredential, state.VerificationTypeCertificate:
		agreementType = state.AgreementTypeEducation
	case state.VerificationTypeHealth:
		agreementType = state.AgreementTypeHealthcare
	}

	if !p.hasActiveAgreementInternal(ic.DAO, targetChainID, agreementType, ic.Block.Index) {
		panic(ErrNoAgreement)
	}

	// Get requester from caller
	requester := ic.VM.GetCallingScriptHash()

	// Create verification request
	requestID := p.incrementCounterInternal(ic.DAO, makePonsVerificationCounterKey())
	vr := &state.VerificationRequest{
		ID:               requestID,
		RequestingChain:  cfg.LocalChainID,
		TargetChain:      targetChainID,
		Subject:          subject,
		VerificationType: verificationType,
		DataHash:         dataHash,
		Status:           state.VerificationPending,
		Requester:        requester,
		CreatedAt:        ic.Block.Index,
		ExpiresAt:        ic.Block.Index + cfg.VerificationTimeout,
	}
	p.setVerificationInternal(ic.DAO, vr)

	ic.AddNotification(p.Hash, VerificationRequestedEvent, stackitem.NewArray([]stackitem.Item{
		stackitem.NewBigInteger(new(big.Int).SetUint64(requestID)),
		stackitem.NewBigInteger(big.NewInt(int64(targetChainID))),
		stackitem.NewByteArray(subject.BytesBE()),
		stackitem.NewBigInteger(big.NewInt(int64(verificationType))),
	}))

	return stackitem.NewBigInteger(new(big.Int).SetUint64(requestID))
}

func (p *Pons) respondVerification(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	requestID := toBigInt(args[0]).Uint64()
	approved := toBool(args[1])
	responseHashBytes, err := args[2].TryBytes()
	if err != nil {
		panic(err)
	}
	responseHash, err := util.Uint256DecodeBytesBE(responseHashBytes)
	if err != nil {
		panic(err)
	}

	if !p.Tutus.CheckCommittee(ic) {
		panic("only committee can respond to verification requests")
	}

	vr, exists := p.getVerificationInternal(ic.DAO, requestID)
	if !exists {
		panic(ErrVerificationNotFound)
	}

	if vr.Status != state.VerificationPending {
		panic("verification request already processed")
	}

	if ic.Block.Index > vr.ExpiresAt {
		vr.Status = state.VerificationExpired
		p.setVerificationInternal(ic.DAO, vr)
		panic(ErrVerificationExpired)
	}

	if approved {
		vr.Status = state.VerificationApproved
	} else {
		vr.Status = state.VerificationRejected
	}
	vr.ResponseHash = responseHash
	vr.RespondedAt = ic.Block.Index
	p.setVerificationInternal(ic.DAO, vr)

	ic.AddNotification(p.Hash, VerificationRespondedEvent, stackitem.NewArray([]stackitem.Item{
		stackitem.NewBigInteger(new(big.Int).SetUint64(requestID)),
		stackitem.NewBool(approved),
	}))

	return stackitem.NewBool(true)
}

func (p *Pons) getVerificationRequest(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	requestID := toBigInt(args[0]).Uint64()

	vr, exists := p.getVerificationInternal(ic.DAO, requestID)
	if !exists {
		return stackitem.Null{}
	}

	return stackitem.NewArray([]stackitem.Item{
		stackitem.NewBigInteger(new(big.Int).SetUint64(vr.ID)),
		stackitem.NewBigInteger(big.NewInt(int64(vr.RequestingChain))),
		stackitem.NewBigInteger(big.NewInt(int64(vr.TargetChain))),
		stackitem.NewByteArray(vr.Subject.BytesBE()),
		stackitem.NewBigInteger(big.NewInt(int64(vr.VerificationType))),
		stackitem.NewByteArray(vr.DataHash.BytesBE()),
		stackitem.NewBigInteger(big.NewInt(int64(vr.Status))),
		stackitem.NewByteArray(vr.ResponseHash.BytesBE()),
		stackitem.NewByteArray(vr.Requester.BytesBE()),
		stackitem.NewBigInteger(big.NewInt(int64(vr.CreatedAt))),
		stackitem.NewBigInteger(big.NewInt(int64(vr.ExpiresAt))),
		stackitem.NewBigInteger(big.NewInt(int64(vr.RespondedAt))),
	})
}

func (p *Pons) requestSettlement(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	toChainID := uint32(toBigInt(args[0]).Int64())
	receiver := toUint160(args[1])
	amount := toBigInt(args[2]).Uint64()
	reference := toString(args[3])

	cfg := p.getConfigInternal(ic.DAO)

	// Check for settlement agreement
	if !p.hasActiveAgreementInternal(ic.DAO, toChainID, state.AgreementTypeSettlement, ic.Block.Index) {
		panic(ErrNoAgreement)
	}

	sender := ic.VM.GetCallingScriptHash()

	// Create settlement request
	settlementID := p.incrementCounterInternal(ic.DAO, makePonsSettlementCounterKey())
	sr := &state.SettlementRequest{
		ID:        settlementID,
		FromChain: cfg.LocalChainID,
		ToChain:   toChainID,
		Sender:    sender,
		Receiver:  receiver,
		Amount:    amount,
		Status:    state.SettlementPending,
		Reference: reference,
		CreatedAt: ic.Block.Index,
	}
	p.setSettlementInternal(ic.DAO, sr)

	ic.AddNotification(p.Hash, SettlementRequestedEvent, stackitem.NewArray([]stackitem.Item{
		stackitem.NewBigInteger(new(big.Int).SetUint64(settlementID)),
		stackitem.NewBigInteger(big.NewInt(int64(toChainID))),
		stackitem.NewByteArray(receiver.BytesBE()),
		stackitem.NewBigInteger(new(big.Int).SetUint64(amount)),
	}))

	return stackitem.NewBigInteger(new(big.Int).SetUint64(settlementID))
}

func (p *Pons) completeSettlement(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	settlementID := toBigInt(args[0]).Uint64()
	txHashBytes, err := args[1].TryBytes()
	if err != nil {
		panic(err)
	}
	txHash, err := util.Uint256DecodeBytesBE(txHashBytes)
	if err != nil {
		panic(err)
	}

	if !p.Tutus.CheckCommittee(ic) {
		panic("only committee can complete settlements")
	}

	sr, exists := p.getSettlementInternal(ic.DAO, settlementID)
	if !exists {
		panic(ErrSettlementNotFound)
	}

	if sr.Status != state.SettlementPending {
		panic("settlement already processed")
	}

	sr.Status = state.SettlementCompleted
	sr.SettledAt = ic.Block.Index
	sr.TxHash = txHash
	p.setSettlementInternal(ic.DAO, sr)

	ic.AddNotification(p.Hash, SettlementCompletedEvent, stackitem.NewArray([]stackitem.Item{
		stackitem.NewBigInteger(new(big.Int).SetUint64(settlementID)),
		stackitem.NewByteArray(txHash.BytesBE()),
	}))

	return stackitem.NewBool(true)
}

func (p *Pons) cancelSettlement(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	settlementID := toBigInt(args[0]).Uint64()

	sr, exists := p.getSettlementInternal(ic.DAO, settlementID)
	if !exists {
		panic(ErrSettlementNotFound)
	}

	if sr.Status != state.SettlementPending {
		panic("settlement already processed")
	}

	// Allow sender or committee to cancel
	caller := ic.VM.GetCallingScriptHash()
	if !caller.Equals(sr.Sender) && !p.Tutus.CheckCommittee(ic) {
		panic("only sender or committee can cancel settlement")
	}

	sr.Status = state.SettlementCancelled
	p.setSettlementInternal(ic.DAO, sr)

	return stackitem.NewBool(true)
}

func (p *Pons) getSettlementRequest(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	settlementID := toBigInt(args[0]).Uint64()

	sr, exists := p.getSettlementInternal(ic.DAO, settlementID)
	if !exists {
		return stackitem.Null{}
	}

	return stackitem.NewArray([]stackitem.Item{
		stackitem.NewBigInteger(new(big.Int).SetUint64(sr.ID)),
		stackitem.NewBigInteger(big.NewInt(int64(sr.FromChain))),
		stackitem.NewBigInteger(big.NewInt(int64(sr.ToChain))),
		stackitem.NewByteArray(sr.Sender.BytesBE()),
		stackitem.NewByteArray(sr.Receiver.BytesBE()),
		stackitem.NewBigInteger(new(big.Int).SetUint64(sr.Amount)),
		stackitem.NewBigInteger(big.NewInt(int64(sr.Status))),
		stackitem.NewByteArray(sr.TxHash.BytesBE()),
		stackitem.NewByteArray([]byte(sr.Reference)),
		stackitem.NewBigInteger(big.NewInt(int64(sr.CreatedAt))),
		stackitem.NewBigInteger(big.NewInt(int64(sr.SettledAt))),
	})
}

func (p *Pons) shareCredential(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	targetChainID := uint32(toBigInt(args[0]).Int64())
	credentialType := state.VerificationType(toBigInt(args[1]).Int64())
	credentialID := toBigInt(args[2]).Uint64()
	contentHashBytes, err := args[3].TryBytes()
	if err != nil {
		panic(err)
	}
	contentHash, err := util.Uint256DecodeBytesBE(contentHashBytes)
	if err != nil {
		panic(err)
	}
	validUntil := uint32(toBigInt(args[4]).Int64())

	cfg := p.getConfigInternal(ic.DAO)

	// Determine agreement type needed
	agreementType := state.AgreementTypeEducation
	if credentialType == state.VerificationTypeHealth {
		agreementType = state.AgreementTypeHealthcare
	}

	if !p.hasActiveAgreementInternal(ic.DAO, targetChainID, agreementType, ic.Block.Index) {
		panic(ErrNoAgreement)
	}

	owner := ic.VM.GetCallingScriptHash()

	// Set default validity if not provided
	if validUntil == 0 {
		validUntil = ic.Block.Index + cfg.CredentialShareExpiry
	}

	// Create credential share
	shareID := p.incrementCounterInternal(ic.DAO, makePonsCredentialCounterKey())
	cs := &state.CredentialShare{
		ID:             shareID,
		SourceChain:    cfg.LocalChainID,
		TargetChain:    targetChainID,
		Owner:          owner,
		CredentialType: credentialType,
		CredentialID:   credentialID,
		ContentHash:    contentHash,
		ValidUntil:     validUntil,
		CreatedAt:      ic.Block.Index,
		IsRevoked:      false,
	}
	p.setCredentialShareInternal(ic.DAO, cs)

	ic.AddNotification(p.Hash, CredentialSharedEvent, stackitem.NewArray([]stackitem.Item{
		stackitem.NewBigInteger(new(big.Int).SetUint64(shareID)),
		stackitem.NewByteArray(owner.BytesBE()),
		stackitem.NewBigInteger(big.NewInt(int64(targetChainID))),
		stackitem.NewBigInteger(big.NewInt(int64(credentialType))),
	}))

	return stackitem.NewBigInteger(new(big.Int).SetUint64(shareID))
}

func (p *Pons) revokeCredentialShare(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	shareID := toBigInt(args[0]).Uint64()

	cs, exists := p.getCredentialShareInternal(ic.DAO, shareID)
	if !exists {
		panic(ErrCredentialNotFound)
	}

	// Allow owner or committee to revoke
	caller := ic.VM.GetCallingScriptHash()
	if !caller.Equals(cs.Owner) && !p.Tutus.CheckCommittee(ic) {
		panic(ErrNotCredentialOwner)
	}

	cs.IsRevoked = true
	p.setCredentialShareInternal(ic.DAO, cs)

	ic.AddNotification(p.Hash, CredentialRevokedEvent, stackitem.NewArray([]stackitem.Item{
		stackitem.NewBigInteger(new(big.Int).SetUint64(shareID)),
		stackitem.NewByteArray(cs.Owner.BytesBE()),
	}))

	return stackitem.NewBool(true)
}

func (p *Pons) getCredentialShare(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	shareID := toBigInt(args[0]).Uint64()

	cs, exists := p.getCredentialShareInternal(ic.DAO, shareID)
	if !exists {
		return stackitem.Null{}
	}

	return stackitem.NewArray([]stackitem.Item{
		stackitem.NewBigInteger(new(big.Int).SetUint64(cs.ID)),
		stackitem.NewBigInteger(big.NewInt(int64(cs.SourceChain))),
		stackitem.NewBigInteger(big.NewInt(int64(cs.TargetChain))),
		stackitem.NewByteArray(cs.Owner.BytesBE()),
		stackitem.NewBigInteger(big.NewInt(int64(cs.CredentialType))),
		stackitem.NewBigInteger(new(big.Int).SetUint64(cs.CredentialID)),
		stackitem.NewByteArray(cs.ContentHash.BytesBE()),
		stackitem.NewBigInteger(big.NewInt(int64(cs.ValidUntil))),
		stackitem.NewBigInteger(big.NewInt(int64(cs.CreatedAt))),
		stackitem.NewBool(cs.IsRevoked),
	})
}

func (p *Pons) verifyCredentialShare(ic *interop.Context, args []stackitem.Item) stackitem.Item {
	shareID := toBigInt(args[0]).Uint64()

	cs, exists := p.getCredentialShareInternal(ic.DAO, shareID)
	if !exists {
		return stackitem.NewBool(false)
	}

	if cs.IsRevoked {
		return stackitem.NewBool(false)
	}

	if ic.Block.Index > cs.ValidUntil {
		return stackitem.NewBool(false)
	}

	return stackitem.NewBool(true)
}

func (p *Pons) getAgreementCount(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
	count := p.getCounterInternal(ic.DAO, makePonsAgreementCounterKey())
	return stackitem.NewBigInteger(new(big.Int).SetUint64(count))
}

func (p *Pons) getVerificationCount(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
	count := p.getCounterInternal(ic.DAO, makePonsVerificationCounterKey())
	return stackitem.NewBigInteger(new(big.Int).SetUint64(count))
}

func (p *Pons) getSettlementCount(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
	count := p.getCounterInternal(ic.DAO, makePonsSettlementCounterKey())
	return stackitem.NewBigInteger(new(big.Int).SetUint64(count))
}

func (p *Pons) getCredentialShareCount(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
	count := p.getCounterInternal(ic.DAO, makePonsCredentialCounterKey())
	return stackitem.NewBigInteger(new(big.Int).SetUint64(count))
}
