package state

import (
	"errors"
	"math/big"

	"git.marketally.com/tutus-one/tutus-chain/pkg/io"
	"git.marketally.com/tutus-one/tutus-chain/pkg/util"
	"git.marketally.com/tutus-one/tutus-chain/pkg/vm/stackitem"
)

var errAncoraInvalidStackItem = errors.New("invalid stack item")

// DataType represents the type of off-chain data being anchored.
type DataType uint8

const (
	// DataTypeMedical is for Salus healthcare records (HIPAA protected).
	DataTypeMedical DataType = 0
	// DataTypeEducation is for Scire education records (certifications, transcripts).
	DataTypeEducation DataType = 1
	// DataTypeInvestment is for Collocatio investment records (portfolios, transactions).
	DataTypeInvestment DataType = 2
	// DataTypeDocuments is for personal documents (IPFS CIDs).
	DataTypeDocuments DataType = 3
	// DataTypePresence is for VPP presence attestations (real-time humanity verification).
	DataTypePresence DataType = 4
	// DataTypeCustom is for government-defined extensions.
	DataTypeCustom DataType = 5
)

// TreeAlgorithm represents the Merkle tree hash algorithm.
type TreeAlgorithm uint8

const (
	// TreeAlgorithmSHA256 is the default SHA256 algorithm.
	TreeAlgorithmSHA256 TreeAlgorithm = 0
	// TreeAlgorithmKeccak256 is Keccak256 for EVM compatibility.
	TreeAlgorithmKeccak256 TreeAlgorithm = 1
	// TreeAlgorithmPoseidon is Poseidon for ZK-proof friendliness.
	TreeAlgorithmPoseidon TreeAlgorithm = 2
)

// ErasureStatus represents the status of a GDPR erasure request.
type ErasureStatus uint8

const (
	// ErasurePending means the request is awaiting off-chain deletion.
	ErasurePending ErasureStatus = 0
	// ErasureConfirmed means off-chain deletion has been confirmed.
	ErasureConfirmed ErasureStatus = 1
	// ErasureDenied means the erasure was denied (legal hold, etc.).
	ErasureDenied ErasureStatus = 2
)

// RootInfo contains metadata about a Merkle root anchored on-chain.
type RootInfo struct {
	// Root is the 32-byte Merkle root.
	Root []byte
	// LeafCount is the number of leaves in the tree.
	LeafCount uint64
	// UpdatedAt is the block height of the last update.
	UpdatedAt uint32
	// UpdatedBy is the provider script hash that updated this root.
	UpdatedBy util.Uint160
	// Version is the incrementing version number.
	Version uint64
	// TreeAlgorithm is the hash algorithm used (0=SHA256, 1=Keccak256, 2=Poseidon).
	TreeAlgorithm TreeAlgorithm
	// SchemaVersion is the data schema version (e.g., "1.0.0").
	SchemaVersion string
	// ContentHash is an optional hash of the serialized tree.
	ContentHash []byte
}

// EncodeBinary implements io.Serializable.
func (r *RootInfo) EncodeBinary(w *io.BinWriter) {
	w.WriteVarBytes(r.Root)
	w.WriteU64LE(r.LeafCount)
	w.WriteU32LE(r.UpdatedAt)
	r.UpdatedBy.EncodeBinary(w)
	w.WriteU64LE(r.Version)
	w.WriteB(byte(r.TreeAlgorithm))
	w.WriteString(r.SchemaVersion)
	w.WriteVarBytes(r.ContentHash)
}

// DecodeBinary implements io.Serializable.
func (r *RootInfo) DecodeBinary(br *io.BinReader) {
	r.Root = br.ReadVarBytes()
	r.LeafCount = br.ReadU64LE()
	r.UpdatedAt = br.ReadU32LE()
	r.UpdatedBy.DecodeBinary(br)
	r.Version = br.ReadU64LE()
	r.TreeAlgorithm = TreeAlgorithm(br.ReadB())
	r.SchemaVersion = br.ReadString()
	r.ContentHash = br.ReadVarBytes()
}

// ToStackItem converts RootInfo to a stack item for VM.
func (r *RootInfo) ToStackItem() (stackitem.Item, error) {
	return stackitem.NewArray([]stackitem.Item{
		stackitem.NewByteArray(r.Root),
		stackitem.NewBigInteger(big.NewInt(int64(r.LeafCount))),
		stackitem.NewBigInteger(big.NewInt(int64(r.UpdatedAt))),
		stackitem.NewByteArray(r.UpdatedBy.BytesBE()),
		stackitem.NewBigInteger(big.NewInt(int64(r.Version))),
		stackitem.NewBigInteger(big.NewInt(int64(r.TreeAlgorithm))),
		stackitem.NewByteArray([]byte(r.SchemaVersion)),
		stackitem.NewByteArray(r.ContentHash),
	}), nil
}

// FromStackItem populates RootInfo from a stack item.
func (r *RootInfo) FromStackItem(item stackitem.Item) error {
	arr, ok := item.Value().([]stackitem.Item)
	if !ok || len(arr) < 8 {
		return errAncoraInvalidStackItem
	}

	root, err := arr[0].TryBytes()
	if err != nil {
		return err
	}
	r.Root = root

	leafCount, err := arr[1].TryInteger()
	if err != nil {
		return err
	}
	r.LeafCount = leafCount.Uint64()

	updatedAt, err := arr[2].TryInteger()
	if err != nil {
		return err
	}
	r.UpdatedAt = uint32(updatedAt.Uint64())

	updatedBy, err := arr[3].TryBytes()
	if err != nil {
		return err
	}
	r.UpdatedBy, err = util.Uint160DecodeBytesBE(updatedBy)
	if err != nil {
		return err
	}

	version, err := arr[4].TryInteger()
	if err != nil {
		return err
	}
	r.Version = version.Uint64()

	algo, err := arr[5].TryInteger()
	if err != nil {
		return err
	}
	r.TreeAlgorithm = TreeAlgorithm(algo.Uint64())

	schema, err := arr[6].TryBytes()
	if err != nil {
		return err
	}
	r.SchemaVersion = string(schema)

	contentHash, err := arr[7].TryBytes()
	if err != nil {
		return err
	}
	r.ContentHash = contentHash

	return nil
}

// ErasureInfo contains metadata about a GDPR erasure request.
type ErasureInfo struct {
	// RequestedAt is the block height when erasure was requested.
	RequestedAt uint32
	// RequestedBy is the script hash of the requester.
	RequestedBy util.Uint160
	// Reason is the GDPR reason code.
	Reason string
	// Status is the current erasure status.
	Status ErasureStatus
	// ProcessedAt is when off-chain deletion was confirmed.
	ProcessedAt uint32
	// ConfirmedBy is the provider that confirmed deletion.
	ConfirmedBy util.Uint160
	// DeniedReason is the reason for denial (if denied).
	DeniedReason string
}

// EncodeBinary implements io.Serializable.
func (e *ErasureInfo) EncodeBinary(w *io.BinWriter) {
	w.WriteU32LE(e.RequestedAt)
	e.RequestedBy.EncodeBinary(w)
	w.WriteString(e.Reason)
	w.WriteB(byte(e.Status))
	w.WriteU32LE(e.ProcessedAt)
	e.ConfirmedBy.EncodeBinary(w)
	w.WriteString(e.DeniedReason)
}

// DecodeBinary implements io.Serializable.
func (e *ErasureInfo) DecodeBinary(br *io.BinReader) {
	e.RequestedAt = br.ReadU32LE()
	e.RequestedBy.DecodeBinary(br)
	e.Reason = br.ReadString()
	e.Status = ErasureStatus(br.ReadB())
	e.ProcessedAt = br.ReadU32LE()
	e.ConfirmedBy.DecodeBinary(br)
	e.DeniedReason = br.ReadString()
}

// ToStackItem converts ErasureInfo to a stack item for VM.
func (e *ErasureInfo) ToStackItem() (stackitem.Item, error) {
	return stackitem.NewArray([]stackitem.Item{
		stackitem.NewBigInteger(big.NewInt(int64(e.RequestedAt))),
		stackitem.NewByteArray(e.RequestedBy.BytesBE()),
		stackitem.NewByteArray([]byte(e.Reason)),
		stackitem.NewBigInteger(big.NewInt(int64(e.Status))),
		stackitem.NewBigInteger(big.NewInt(int64(e.ProcessedAt))),
		stackitem.NewByteArray(e.ConfirmedBy.BytesBE()),
		stackitem.NewByteArray([]byte(e.DeniedReason)),
	}), nil
}

// FromStackItem populates ErasureInfo from a stack item.
func (e *ErasureInfo) FromStackItem(item stackitem.Item) error {
	arr, ok := item.Value().([]stackitem.Item)
	if !ok || len(arr) < 7 {
		return errAncoraInvalidStackItem
	}

	requestedAt, err := arr[0].TryInteger()
	if err != nil {
		return err
	}
	e.RequestedAt = uint32(requestedAt.Uint64())

	requestedBy, err := arr[1].TryBytes()
	if err != nil {
		return err
	}
	e.RequestedBy, err = util.Uint160DecodeBytesBE(requestedBy)
	if err != nil {
		return err
	}

	reason, err := arr[2].TryBytes()
	if err != nil {
		return err
	}
	e.Reason = string(reason)

	status, err := arr[3].TryInteger()
	if err != nil {
		return err
	}
	e.Status = ErasureStatus(status.Uint64())

	processedAt, err := arr[4].TryInteger()
	if err != nil {
		return err
	}
	e.ProcessedAt = uint32(processedAt.Uint64())

	confirmedBy, err := arr[5].TryBytes()
	if err != nil {
		return err
	}
	e.ConfirmedBy, err = util.Uint160DecodeBytesBE(confirmedBy)
	if err != nil {
		return err
	}

	deniedReason, err := arr[6].TryBytes()
	if err != nil {
		return err
	}
	e.DeniedReason = string(deniedReason)

	return nil
}

// ProviderConfig contains configuration for an authorized data provider.
type ProviderConfig struct {
	// DataType is the data type this provider is authorized for.
	DataType DataType
	// Provider is the script hash of the authorized provider.
	Provider util.Uint160
	// Description is a human-readable description.
	Description string
	// RegisteredAt is the block height when registered.
	RegisteredAt uint32
	// Active indicates if the provider is currently active.
	Active bool
	// MaxUpdatesPerBlock is the anti-spam rate limit.
	MaxUpdatesPerBlock uint32
	// UpdateCooldown is the blocks between updates per VitaID.
	UpdateCooldown uint32
}

// EncodeBinary implements io.Serializable.
func (p *ProviderConfig) EncodeBinary(w *io.BinWriter) {
	w.WriteB(byte(p.DataType))
	p.Provider.EncodeBinary(w)
	w.WriteString(p.Description)
	w.WriteU32LE(p.RegisteredAt)
	w.WriteBool(p.Active)
	w.WriteU32LE(p.MaxUpdatesPerBlock)
	w.WriteU32LE(p.UpdateCooldown)
}

// DecodeBinary implements io.Serializable.
func (p *ProviderConfig) DecodeBinary(br *io.BinReader) {
	p.DataType = DataType(br.ReadB())
	p.Provider.DecodeBinary(br)
	p.Description = br.ReadString()
	p.RegisteredAt = br.ReadU32LE()
	p.Active = br.ReadBool()
	p.MaxUpdatesPerBlock = br.ReadU32LE()
	p.UpdateCooldown = br.ReadU32LE()
}

// ToStackItem converts ProviderConfig to a stack item for VM.
func (p *ProviderConfig) ToStackItem() (stackitem.Item, error) {
	return stackitem.NewArray([]stackitem.Item{
		stackitem.NewBigInteger(big.NewInt(int64(p.DataType))),
		stackitem.NewByteArray(p.Provider.BytesBE()),
		stackitem.NewByteArray([]byte(p.Description)),
		stackitem.NewBigInteger(big.NewInt(int64(p.RegisteredAt))),
		stackitem.NewBool(p.Active),
		stackitem.NewBigInteger(big.NewInt(int64(p.MaxUpdatesPerBlock))),
		stackitem.NewBigInteger(big.NewInt(int64(p.UpdateCooldown))),
	}), nil
}

// FromStackItem populates ProviderConfig from a stack item.
func (p *ProviderConfig) FromStackItem(item stackitem.Item) error {
	arr, ok := item.Value().([]stackitem.Item)
	if !ok || len(arr) < 7 {
		return errAncoraInvalidStackItem
	}

	dataType, err := arr[0].TryInteger()
	if err != nil {
		return err
	}
	p.DataType = DataType(dataType.Uint64())

	provider, err := arr[1].TryBytes()
	if err != nil {
		return err
	}
	p.Provider, err = util.Uint160DecodeBytesBE(provider)
	if err != nil {
		return err
	}

	description, err := arr[2].TryBytes()
	if err != nil {
		return err
	}
	p.Description = string(description)

	registeredAt, err := arr[3].TryInteger()
	if err != nil {
		return err
	}
	p.RegisteredAt = uint32(registeredAt.Uint64())

	active, err := arr[4].TryBool()
	if err != nil {
		return err
	}
	p.Active = active

	maxUpdates, err := arr[5].TryInteger()
	if err != nil {
		return err
	}
	p.MaxUpdatesPerBlock = uint32(maxUpdates.Uint64())

	cooldown, err := arr[6].TryInteger()
	if err != nil {
		return err
	}
	p.UpdateCooldown = uint32(cooldown.Uint64())

	return nil
}

// StateAnchorsConfig contains configuration for the StateAnchors contract.
type StateAnchorsConfig struct {
	// DefaultTreeAlgorithm is the default hash algorithm (0=SHA256).
	DefaultTreeAlgorithm TreeAlgorithm
	// MaxProofDepth is the maximum depth of Merkle proofs (default: 32).
	MaxProofDepth uint32
	// DefaultMaxUpdatesPerBlock is the default rate limit.
	DefaultMaxUpdatesPerBlock uint32
	// DefaultUpdateCooldown is the default cooldown in blocks.
	DefaultUpdateCooldown uint32
	// MaxHistoryVersions is the max history to retain per vitaID+dataType.
	MaxHistoryVersions uint32
	// ErasureGracePeriod is blocks before erasure can be denied.
	ErasureGracePeriod uint32
	// AttestationValidBlocks is how long attestations are valid.
	AttestationValidBlocks uint32
}

// DefaultStateAnchorsConfig returns the default configuration.
func DefaultStateAnchorsConfig() StateAnchorsConfig {
	return StateAnchorsConfig{
		DefaultTreeAlgorithm:      TreeAlgorithmSHA256,
		MaxProofDepth:             32,
		DefaultMaxUpdatesPerBlock: 10,
		DefaultUpdateCooldown:     1,
		MaxHistoryVersions:        100,
		ErasureGracePeriod:        1000,  // ~16 minutes at 1s blocks
		AttestationValidBlocks:    86400, // ~24 hours
	}
}
