package state

import (
	"errors"
	"fmt"
	"math"
	"math/big"

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

// TokenStatus represents the status of a Vita token.
type TokenStatus uint8

const (
	// TokenStatusActive indicates an active token.
	TokenStatusActive TokenStatus = 0
	// TokenStatusSuspended indicates a temporarily suspended token.
	TokenStatusSuspended TokenStatus = 1
	// TokenStatusRevoked indicates a permanently revoked token.
	TokenStatusRevoked TokenStatus = 2
	// TokenStatusRecovering indicates recovery is in progress.
	TokenStatusRecovering TokenStatus = 3
)

// DisclosureLevel represents the visibility level of an attribute.
type DisclosureLevel uint8

const (
	// DisclosurePrivate means only the owner can see the attribute.
	DisclosurePrivate DisclosureLevel = 0
	// DisclosureVerifier means owner and designated verifiers can see it.
	DisclosureVerifier DisclosureLevel = 1
	// DisclosureRole means owner and callers with specified roles can see it.
	DisclosureRole DisclosureLevel = 2
	// DisclosurePublic means anyone can see the attribute.
	DisclosurePublic DisclosureLevel = 3
)

// RecoveryStatus represents the status of a recovery request.
type RecoveryStatus uint8

const (
	// RecoveryStatusPending indicates recovery is pending approval.
	RecoveryStatusPending RecoveryStatus = 0
	// RecoveryStatusApproved indicates recovery has been approved.
	RecoveryStatusApproved RecoveryStatus = 1
	// RecoveryStatusExecuted indicates recovery has been executed.
	RecoveryStatusExecuted RecoveryStatus = 2
	// RecoveryStatusDenied indicates recovery has been denied.
	RecoveryStatusDenied RecoveryStatus = 3
	// RecoveryStatusExpired indicates recovery request has expired.
	RecoveryStatusExpired RecoveryStatus = 4
)

// PresenceStatus represents the online presence status of a Vita holder.
type PresenceStatus uint8

const (
	// PresenceStatusOffline indicates no recent heartbeat.
	PresenceStatusOffline PresenceStatus = 0
	// PresenceStatusOnline indicates active heartbeat within presence window.
	PresenceStatusOnline PresenceStatus = 1
	// PresenceStatusAway indicates heartbeat within extended window but outside primary window.
	PresenceStatusAway PresenceStatus = 2
	// PresenceStatusInvisible indicates heartbeats are sent but status is hidden.
	PresenceStatusInvisible PresenceStatus = 3
)

// Vita represents a soul-bound identity token.
type Vita struct {
	TokenID      uint64       // Unique sequential identifier
	Owner        util.Uint160 // Owner's script hash
	PersonHash   []byte       // Hash of biometric/identity proof
	IsEntity     bool         // True if organization, false if natural person
	CreatedAt    uint32       // Block height when created
	UpdatedAt    uint32       // Block height of last modification
	Status       TokenStatus  // Current status
	StatusReason string       // Reason for status change
	RecoveryHash []byte       // Hash of recovery mechanism
	VestedUntil  uint32       // Block height until which the Vita is vesting (Sybil resistance)
}

// ToStackItem implements stackitem.Convertible interface.
func (t *Vita) ToStackItem() (stackitem.Item, error) {
	return stackitem.NewStruct([]stackitem.Item{
		stackitem.NewBigInteger(big.NewInt(int64(t.TokenID))),
		stackitem.NewByteArray(t.Owner.BytesBE()),
		stackitem.NewByteArray(t.PersonHash),
		stackitem.NewBool(t.IsEntity),
		stackitem.NewBigInteger(big.NewInt(int64(t.CreatedAt))),
		stackitem.NewBigInteger(big.NewInt(int64(t.UpdatedAt))),
		stackitem.NewBigInteger(big.NewInt(int64(t.Status))),
		stackitem.NewByteArray([]byte(t.StatusReason)),
		stackitem.NewByteArray(t.RecoveryHash),
		stackitem.NewBigInteger(big.NewInt(int64(t.VestedUntil))),
	}), nil
}

// FromStackItem implements stackitem.Convertible interface.
func (t *Vita) FromStackItem(item stackitem.Item) error {
	items, ok := item.Value().([]stackitem.Item)
	if !ok {
		return errors.New("not a struct")
	}
	if len(items) != 10 {
		return fmt.Errorf("wrong number of elements: expected 10, got %d", len(items))
	}

	tokenID, err := items[0].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid tokenID: %w", err)
	}
	t.TokenID = tokenID.Uint64()

	ownerBytes, err := items[1].TryBytes()
	if err != nil {
		return fmt.Errorf("invalid owner: %w", err)
	}
	t.Owner, err = util.Uint160DecodeBytesBE(ownerBytes)
	if err != nil {
		return fmt.Errorf("invalid owner hash: %w", err)
	}

	t.PersonHash, err = items[2].TryBytes()
	if err != nil {
		return fmt.Errorf("invalid personHash: %w", err)
	}

	isEntity, err := items[3].TryBool()
	if err != nil {
		return fmt.Errorf("invalid isEntity: %w", err)
	}
	t.IsEntity = isEntity

	createdAt, err := items[4].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid createdAt: %w", err)
	}
	t.CreatedAt = uint32(createdAt.Int64())

	updatedAt, err := items[5].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid updatedAt: %w", err)
	}
	t.UpdatedAt = uint32(updatedAt.Int64())

	status, err := items[6].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid status: %w", err)
	}
	t.Status = TokenStatus(status.Int64())

	statusReasonBytes, err := items[7].TryBytes()
	if err != nil {
		return fmt.Errorf("invalid statusReason: %w", err)
	}
	t.StatusReason = string(statusReasonBytes)

	t.RecoveryHash, err = items[8].TryBytes()
	if err != nil {
		return fmt.Errorf("invalid recoveryHash: %w", err)
	}

	vestedUntil, err := items[9].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid vestedUntil: %w", err)
	}
	t.VestedUntil = uint32(vestedUntil.Int64())

	return nil
}

// Attribute represents an identity attribute with disclosure control.
type Attribute struct {
	Key             string          // Attribute name
	ValueHash       []byte          // Hash of value (privacy)
	ValueEnc        []byte          // Encrypted value (optional)
	Attestor        util.Uint160    // Who attested this (script hash)
	AttestedAt      uint32          // Block height when attested
	ExpiresAt       uint32          // Optional expiration (0 = never)
	Revoked         bool            // Whether attribute has been revoked
	RevokedAt       uint32          // Block height when revoked
	DisclosureLevel DisclosureLevel // Visibility level
}

// ToStackItem implements stackitem.Convertible interface.
func (a *Attribute) ToStackItem() (stackitem.Item, error) {
	return stackitem.NewStruct([]stackitem.Item{
		stackitem.NewByteArray([]byte(a.Key)),
		stackitem.NewByteArray(a.ValueHash),
		stackitem.NewByteArray(a.ValueEnc),
		stackitem.NewByteArray(a.Attestor.BytesBE()),
		stackitem.NewBigInteger(big.NewInt(int64(a.AttestedAt))),
		stackitem.NewBigInteger(big.NewInt(int64(a.ExpiresAt))),
		stackitem.NewBool(a.Revoked),
		stackitem.NewBigInteger(big.NewInt(int64(a.RevokedAt))),
		stackitem.NewBigInteger(big.NewInt(int64(a.DisclosureLevel))),
	}), nil
}

// FromStackItem implements stackitem.Convertible interface.
func (a *Attribute) FromStackItem(item stackitem.Item) error {
	items, ok := item.Value().([]stackitem.Item)
	if !ok {
		return errors.New("not a struct")
	}
	if len(items) != 9 {
		return fmt.Errorf("wrong number of elements: expected 9, got %d", len(items))
	}

	keyBytes, err := items[0].TryBytes()
	if err != nil {
		return fmt.Errorf("invalid key: %w", err)
	}
	a.Key = string(keyBytes)

	a.ValueHash, err = items[1].TryBytes()
	if err != nil {
		return fmt.Errorf("invalid valueHash: %w", err)
	}

	a.ValueEnc, err = items[2].TryBytes()
	if err != nil {
		return fmt.Errorf("invalid valueEnc: %w", err)
	}

	attestorBytes, err := items[3].TryBytes()
	if err != nil {
		return fmt.Errorf("invalid attestor: %w", err)
	}
	a.Attestor, err = util.Uint160DecodeBytesBE(attestorBytes)
	if err != nil {
		return fmt.Errorf("invalid attestor hash: %w", err)
	}

	attestedAt, err := items[4].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid attestedAt: %w", err)
	}
	a.AttestedAt = uint32(attestedAt.Int64())

	expiresAt, err := items[5].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid expiresAt: %w", err)
	}
	a.ExpiresAt = uint32(expiresAt.Int64())

	a.Revoked, err = items[6].TryBool()
	if err != nil {
		return fmt.Errorf("invalid revoked: %w", err)
	}

	revokedAt, err := items[7].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid revokedAt: %w", err)
	}
	a.RevokedAt = uint32(revokedAt.Int64())

	disclosureLevel, err := items[8].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid disclosureLevel: %w", err)
	}
	a.DisclosureLevel = DisclosureLevel(disclosureLevel.Int64())

	return nil
}

// AuthChallenge represents a passwordless authentication challenge.
type AuthChallenge struct {
	ChallengeID util.Uint256 // Unique challenge identifier
	TokenID     uint64       // Associated Vita token
	Nonce       []byte       // Random bytes to sign
	CreatedAt   uint32       // Block height when created
	ExpiresAt   uint32       // Block height when expires
	Purpose     string       // "login", "sign", "recover"
	Fulfilled   bool         // Whether challenge has been fulfilled
	FulfilledAt uint32       // Block height when fulfilled
}

// ToStackItem implements stackitem.Convertible interface.
func (c *AuthChallenge) ToStackItem() (stackitem.Item, error) {
	return stackitem.NewStruct([]stackitem.Item{
		stackitem.NewByteArray(c.ChallengeID.BytesBE()),
		stackitem.NewBigInteger(big.NewInt(int64(c.TokenID))),
		stackitem.NewByteArray(c.Nonce),
		stackitem.NewBigInteger(big.NewInt(int64(c.CreatedAt))),
		stackitem.NewBigInteger(big.NewInt(int64(c.ExpiresAt))),
		stackitem.NewByteArray([]byte(c.Purpose)),
		stackitem.NewBool(c.Fulfilled),
		stackitem.NewBigInteger(big.NewInt(int64(c.FulfilledAt))),
	}), nil
}

// FromStackItem implements stackitem.Convertible interface.
func (c *AuthChallenge) FromStackItem(item stackitem.Item) error {
	items, ok := item.Value().([]stackitem.Item)
	if !ok {
		return errors.New("not a struct")
	}
	if len(items) != 8 {
		return fmt.Errorf("wrong number of elements: expected 8, got %d", len(items))
	}

	challengeIDBytes, err := items[0].TryBytes()
	if err != nil {
		return fmt.Errorf("invalid challengeID: %w", err)
	}
	c.ChallengeID, err = util.Uint256DecodeBytesBE(challengeIDBytes)
	if err != nil {
		return fmt.Errorf("invalid challengeID hash: %w", err)
	}

	tokenID, err := items[1].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid tokenID: %w", err)
	}
	c.TokenID = tokenID.Uint64()

	c.Nonce, err = items[2].TryBytes()
	if err != nil {
		return fmt.Errorf("invalid nonce: %w", err)
	}

	createdAt, err := items[3].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid createdAt: %w", err)
	}
	c.CreatedAt = uint32(createdAt.Int64())

	expiresAt, err := items[4].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid expiresAt: %w", err)
	}
	c.ExpiresAt = uint32(expiresAt.Int64())

	purposeBytes, err := items[5].TryBytes()
	if err != nil {
		return fmt.Errorf("invalid purpose: %w", err)
	}
	c.Purpose = string(purposeBytes)

	c.Fulfilled, err = items[6].TryBool()
	if err != nil {
		return fmt.Errorf("invalid fulfilled: %w", err)
	}

	fulfilledAt, err := items[7].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid fulfilledAt: %w", err)
	}
	c.FulfilledAt = uint32(fulfilledAt.Int64())

	return nil
}

// RecoveryRequest represents a key recovery request.
type RecoveryRequest struct {
	RequestID         util.Uint256   // Unique request identifier
	TokenID           uint64         // Token being recovered
	NewOwner          util.Uint160   // Proposed new owner
	Requester         util.Uint160   // Who initiated recovery
	Evidence          []byte         // Encrypted evidence hash
	Approvals         []util.Uint160 // Approvers who have approved
	RequiredApprovals int            // Required number of approvals
	CreatedAt         uint32         // Block height when created
	DelayUntil        uint32         // Block height when executable
	ExpiresAt         uint32         // Block height when expires
	Status            RecoveryStatus // Current status
}

// ToStackItem implements stackitem.Convertible interface.
func (r *RecoveryRequest) ToStackItem() (stackitem.Item, error) {
	approvals := make([]stackitem.Item, len(r.Approvals))
	for i, a := range r.Approvals {
		approvals[i] = stackitem.NewByteArray(a.BytesBE())
	}

	return stackitem.NewStruct([]stackitem.Item{
		stackitem.NewByteArray(r.RequestID.BytesBE()),
		stackitem.NewBigInteger(big.NewInt(int64(r.TokenID))),
		stackitem.NewByteArray(r.NewOwner.BytesBE()),
		stackitem.NewByteArray(r.Requester.BytesBE()),
		stackitem.NewByteArray(r.Evidence),
		stackitem.NewArray(approvals),
		stackitem.NewBigInteger(big.NewInt(int64(r.RequiredApprovals))),
		stackitem.NewBigInteger(big.NewInt(int64(r.CreatedAt))),
		stackitem.NewBigInteger(big.NewInt(int64(r.DelayUntil))),
		stackitem.NewBigInteger(big.NewInt(int64(r.ExpiresAt))),
		stackitem.NewBigInteger(big.NewInt(int64(r.Status))),
	}), nil
}

// FromStackItem implements stackitem.Convertible interface.
func (r *RecoveryRequest) FromStackItem(item stackitem.Item) error {
	items, ok := item.Value().([]stackitem.Item)
	if !ok {
		return errors.New("not a struct")
	}
	if len(items) != 11 {
		return fmt.Errorf("wrong number of elements: expected 11, got %d", len(items))
	}

	requestIDBytes, err := items[0].TryBytes()
	if err != nil {
		return fmt.Errorf("invalid requestID: %w", err)
	}
	r.RequestID, err = util.Uint256DecodeBytesBE(requestIDBytes)
	if err != nil {
		return fmt.Errorf("invalid requestID hash: %w", err)
	}

	tokenID, err := items[1].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid tokenID: %w", err)
	}
	r.TokenID = tokenID.Uint64()

	newOwnerBytes, err := items[2].TryBytes()
	if err != nil {
		return fmt.Errorf("invalid newOwner: %w", err)
	}
	r.NewOwner, err = util.Uint160DecodeBytesBE(newOwnerBytes)
	if err != nil {
		return fmt.Errorf("invalid newOwner hash: %w", err)
	}

	requesterBytes, err := items[3].TryBytes()
	if err != nil {
		return fmt.Errorf("invalid requester: %w", err)
	}
	r.Requester, err = util.Uint160DecodeBytesBE(requesterBytes)
	if err != nil {
		return fmt.Errorf("invalid requester hash: %w", err)
	}

	r.Evidence, err = items[4].TryBytes()
	if err != nil {
		return fmt.Errorf("invalid evidence: %w", err)
	}

	approvalsArray, ok := items[5].Value().([]stackitem.Item)
	if !ok {
		return errors.New("approvals is not an array")
	}
	r.Approvals = make([]util.Uint160, len(approvalsArray))
	for i, a := range approvalsArray {
		approvalBytes, err := a.TryBytes()
		if err != nil {
			return fmt.Errorf("invalid approval %d: %w", i, err)
		}
		r.Approvals[i], err = util.Uint160DecodeBytesBE(approvalBytes)
		if err != nil {
			return fmt.Errorf("invalid approval %d hash: %w", i, err)
		}
	}

	requiredApprovals, err := items[6].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid requiredApprovals: %w", err)
	}
	ra := requiredApprovals.Int64()
	if ra < 0 || ra > math.MaxInt {
		return errors.New("requiredApprovals out of range")
	}
	r.RequiredApprovals = int(ra)

	createdAt, err := items[7].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid createdAt: %w", err)
	}
	r.CreatedAt = uint32(createdAt.Int64())

	delayUntil, err := items[8].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid delayUntil: %w", err)
	}
	r.DelayUntil = uint32(delayUntil.Int64())

	expiresAt, err := items[9].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid expiresAt: %w", err)
	}
	r.ExpiresAt = uint32(expiresAt.Int64())

	status, err := items[10].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid status: %w", err)
	}
	r.Status = RecoveryStatus(status.Int64())

	return nil
}

// PresenceRecord represents a VPP (Vita Presence Protocol) heartbeat record.
// Used for real-time proof of humanity in online applications.
type PresenceRecord struct {
	TokenID       uint64         // Associated Vita token
	LastHeartbeat uint32         // Block height of last heartbeat
	SessionID     []byte         // Current session identifier (32 bytes)
	DeviceHash    []byte         // Hash of device fingerprint (privacy)
	Status        PresenceStatus // Current visibility status
	Invisible     bool           // If true, heartbeats are recorded but status is hidden
}

// ToStackItem implements stackitem.Convertible interface.
func (p *PresenceRecord) ToStackItem() (stackitem.Item, error) {
	return stackitem.NewStruct([]stackitem.Item{
		stackitem.NewBigInteger(big.NewInt(int64(p.TokenID))),
		stackitem.NewBigInteger(big.NewInt(int64(p.LastHeartbeat))),
		stackitem.NewByteArray(p.SessionID),
		stackitem.NewByteArray(p.DeviceHash),
		stackitem.NewBigInteger(big.NewInt(int64(p.Status))),
		stackitem.NewBool(p.Invisible),
	}), nil
}

// FromStackItem implements stackitem.Convertible interface.
func (p *PresenceRecord) FromStackItem(item stackitem.Item) error {
	items, ok := item.Value().([]stackitem.Item)
	if !ok {
		return errors.New("not a struct")
	}
	if len(items) != 6 {
		return fmt.Errorf("wrong number of elements: expected 6, got %d", len(items))
	}

	tokenID, err := items[0].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid tokenID: %w", err)
	}
	p.TokenID = tokenID.Uint64()

	lastHeartbeat, err := items[1].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid lastHeartbeat: %w", err)
	}
	p.LastHeartbeat = uint32(lastHeartbeat.Int64())

	p.SessionID, err = items[2].TryBytes()
	if err != nil {
		return fmt.Errorf("invalid sessionID: %w", err)
	}

	p.DeviceHash, err = items[3].TryBytes()
	if err != nil {
		return fmt.Errorf("invalid deviceHash: %w", err)
	}

	status, err := items[4].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid status: %w", err)
	}
	p.Status = PresenceStatus(status.Int64())

	p.Invisible, err = items[5].TryBool()
	if err != nil {
		return fmt.Errorf("invalid invisible: %w", err)
	}

	return nil
}

// PresenceConfig holds configuration for the VPP presence system.
type PresenceConfig struct {
	// PresenceWindow is the number of blocks within which a heartbeat is considered "online".
	// Default: 60 blocks (~1 minute at 1 second/block)
	PresenceWindow uint32

	// AwayWindow is the extended window for "away" status.
	// Default: 300 blocks (~5 minutes)
	AwayWindow uint32

	// MinHeartbeatInterval is the minimum blocks between heartbeats (rate limiting).
	// Default: 15 blocks (~15 seconds)
	MinHeartbeatInterval uint32

	// MaxHeartbeatInterval is the maximum blocks before a heartbeat is required.
	// Default: 60 blocks (~1 minute)
	MaxHeartbeatInterval uint32
}

// ToStackItem implements stackitem.Convertible interface.
func (c *PresenceConfig) ToStackItem() (stackitem.Item, error) {
	return stackitem.NewStruct([]stackitem.Item{
		stackitem.NewBigInteger(big.NewInt(int64(c.PresenceWindow))),
		stackitem.NewBigInteger(big.NewInt(int64(c.AwayWindow))),
		stackitem.NewBigInteger(big.NewInt(int64(c.MinHeartbeatInterval))),
		stackitem.NewBigInteger(big.NewInt(int64(c.MaxHeartbeatInterval))),
	}), nil
}

// FromStackItem implements stackitem.Convertible interface.
func (c *PresenceConfig) FromStackItem(item stackitem.Item) error {
	items, ok := item.Value().([]stackitem.Item)
	if !ok {
		return errors.New("not a struct")
	}
	if len(items) != 4 {
		return fmt.Errorf("wrong number of elements: expected 4, got %d", len(items))
	}

	presenceWindow, err := items[0].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid presenceWindow: %w", err)
	}
	c.PresenceWindow = uint32(presenceWindow.Int64())

	awayWindow, err := items[1].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid awayWindow: %w", err)
	}
	c.AwayWindow = uint32(awayWindow.Int64())

	minHeartbeat, err := items[2].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid minHeartbeatInterval: %w", err)
	}
	c.MinHeartbeatInterval = uint32(minHeartbeat.Int64())

	maxHeartbeat, err := items[3].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid maxHeartbeatInterval: %w", err)
	}
	c.MaxHeartbeatInterval = uint32(maxHeartbeat.Int64())

	return nil
}

// DefaultPresenceConfig returns the default VPP configuration.
func DefaultPresenceConfig() PresenceConfig {
	return PresenceConfig{
		PresenceWindow:       60,  // ~1 minute
		AwayWindow:           300, // ~5 minutes
		MinHeartbeatInterval: 15,  // ~15 seconds
		MaxHeartbeatInterval: 60,  // ~1 minute
	}
}
