package native

import (
	"encoding/binary"
	"errors"
	"math/big"

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

// LOW-003: Configuration Governance
// Centralized configuration registry for managing system-wide parameters.
// All hardcoded values should be moved here for transparent governance.

// ConfigCategory represents a category of configuration values.
type ConfigCategory uint8

const (
	// ConfigCategorySystem covers core system parameters
	ConfigCategorySystem ConfigCategory = iota
	// ConfigCategoryIdentity covers Vita/identity parameters
	ConfigCategoryIdentity
	// ConfigCategoryEconomic covers VTS/Tribute/economic parameters
	ConfigCategoryEconomic
	// ConfigCategoryGovernance covers voting/committee parameters
	ConfigCategoryGovernance
	// ConfigCategoryHealth covers Salus/healthcare parameters
	ConfigCategoryHealth
	// ConfigCategoryEducation covers Scire/education parameters
	ConfigCategoryEducation
	// ConfigCategoryLegal covers Lex/legal parameters
	ConfigCategoryLegal
	// ConfigCategorySecurity covers security-related parameters
	ConfigCategorySecurity
)

// ConfigEntry represents a single configuration value.
type ConfigEntry struct {
	Key           string         // Unique key (category:name format)
	Category      ConfigCategory // Category for grouping
	ValueType     ConfigValueType
	CurrentValue  []byte         // Current value (serialized)
	DefaultValue  []byte         // Default value (for reference)
	MinValue      []byte         // Minimum allowed value (if applicable)
	MaxValue      []byte         // Maximum allowed value (if applicable)
	Description   string         // Human-readable description
	RequiresVote  bool           // Whether changes require governance vote
	LastModified  uint32         // Block height of last modification
	ModifiedBy    util.Uint160   // Who made the last modification
}

// ConfigValueType represents the type of a configuration value.
type ConfigValueType uint8

const (
	ConfigTypeUint64 ConfigValueType = iota
	ConfigTypeInt64
	ConfigTypeBool
	ConfigTypeString
	ConfigTypeHash160
	ConfigTypeHash256
	ConfigTypeBytes
)

// ConfigChangeProposal represents a pending configuration change.
type ConfigChangeProposal struct {
	ProposalID    uint64
	Key           string
	NewValue      []byte
	Proposer      util.Uint160
	ProposedAt    uint32
	ExpiresAt     uint32
	Approvals     []util.Uint160
	RequiredVotes uint32
	Status        ConfigProposalStatus
}

// ConfigProposalStatus represents the status of a config change proposal.
type ConfigProposalStatus uint8

const (
	ConfigProposalPending ConfigProposalStatus = iota
	ConfigProposalApproved
	ConfigProposalRejected
	ConfigProposalExpired
	ConfigProposalExecuted
)

// Storage prefixes for configuration registry.
const (
	configRegPrefixEntry       byte = 0xC0 // key -> ConfigEntry
	configRegPrefixByCategory  byte = 0xC1 // category + key -> exists
	configRegPrefixProposal    byte = 0xC2 // proposalID -> ConfigChangeProposal
	configRegPrefixProposalCtr byte = 0xCF // -> next proposalID
)

// Configuration errors.
var (
	ErrConfigNotFound       = errors.New("configuration key not found")
	ErrConfigInvalidValue   = errors.New("invalid configuration value")
	ErrConfigOutOfRange     = errors.New("configuration value out of allowed range")
	ErrConfigRequiresVote   = errors.New("configuration change requires governance vote")
	ErrConfigProposalExists = errors.New("pending proposal already exists for this key")
)

// ConfigRegistry manages system-wide configuration.
type ConfigRegistry struct {
	contractID int32
}

// NewConfigRegistry creates a new configuration registry.
func NewConfigRegistry(contractID int32) *ConfigRegistry {
	return &ConfigRegistry{contractID: contractID}
}

// GetEntry retrieves a configuration entry by key.
func (cr *ConfigRegistry) GetEntry(d *dao.Simple, key string) *ConfigEntry {
	storageKey := cr.makeEntryKey(key)
	si := d.GetStorageItem(cr.contractID, storageKey)
	if si == nil {
		return nil
	}
	return cr.deserializeEntry(si)
}

// SetEntry stores a configuration entry.
func (cr *ConfigRegistry) SetEntry(d *dao.Simple, entry *ConfigEntry) {
	storageKey := cr.makeEntryKey(entry.Key)
	data := cr.serializeEntry(entry)
	d.PutStorageItem(cr.contractID, storageKey, data)

	// Also index by category
	catKey := cr.makeCategoryKey(entry.Category, entry.Key)
	d.PutStorageItem(cr.contractID, catKey, []byte{1})
}

// GetUint64 retrieves a uint64 configuration value.
func (cr *ConfigRegistry) GetUint64(d *dao.Simple, key string) (uint64, error) {
	entry := cr.GetEntry(d, key)
	if entry == nil {
		return 0, ErrConfigNotFound
	}
	if entry.ValueType != ConfigTypeUint64 || len(entry.CurrentValue) < 8 {
		return 0, ErrConfigInvalidValue
	}
	return binary.BigEndian.Uint64(entry.CurrentValue), nil
}

// GetUint64OrDefault retrieves a uint64 value or returns default if not found.
func (cr *ConfigRegistry) GetUint64OrDefault(d *dao.Simple, key string, defaultVal uint64) uint64 {
	val, err := cr.GetUint64(d, key)
	if err != nil {
		return defaultVal
	}
	return val
}

// SetUint64 sets a uint64 configuration value.
func (cr *ConfigRegistry) SetUint64(d *dao.Simple, key string, value uint64, modifier util.Uint160, blockHeight uint32) error {
	entry := cr.GetEntry(d, key)
	if entry == nil {
		return ErrConfigNotFound
	}
	if entry.ValueType != ConfigTypeUint64 {
		return ErrConfigInvalidValue
	}

	// Validate range
	if len(entry.MinValue) >= 8 && len(entry.MaxValue) >= 8 {
		minVal := binary.BigEndian.Uint64(entry.MinValue)
		maxVal := binary.BigEndian.Uint64(entry.MaxValue)
		if value < minVal || value > maxVal {
			return ErrConfigOutOfRange
		}
	}

	entry.CurrentValue = make([]byte, 8)
	binary.BigEndian.PutUint64(entry.CurrentValue, value)
	entry.LastModified = blockHeight
	entry.ModifiedBy = modifier
	cr.SetEntry(d, entry)
	return nil
}

// GetBool retrieves a boolean configuration value.
func (cr *ConfigRegistry) GetBool(d *dao.Simple, key string) (bool, error) {
	entry := cr.GetEntry(d, key)
	if entry == nil {
		return false, ErrConfigNotFound
	}
	if entry.ValueType != ConfigTypeBool || len(entry.CurrentValue) < 1 {
		return false, ErrConfigInvalidValue
	}
	return entry.CurrentValue[0] == 1, nil
}

// GetBoolOrDefault retrieves a bool value or returns default if not found.
func (cr *ConfigRegistry) GetBoolOrDefault(d *dao.Simple, key string, defaultVal bool) bool {
	val, err := cr.GetBool(d, key)
	if err != nil {
		return defaultVal
	}
	return val
}

// GetEntriesByCategory retrieves all entries in a category.
func (cr *ConfigRegistry) GetEntriesByCategory(d *dao.Simple, category ConfigCategory) []*ConfigEntry {
	var entries []*ConfigEntry
	prefix := []byte{configRegPrefixByCategory, byte(category)}

	d.Seek(cr.contractID, storage.SeekRange{Prefix: prefix}, func(k, v []byte) bool {
		if len(k) > 1 {
			key := string(k[1:]) // Skip category byte
			entry := cr.GetEntry(d, key)
			if entry != nil {
				entries = append(entries, entry)
			}
		}
		return true
	})

	return entries
}

// RegisterConfig registers a new configuration entry with defaults.
func (cr *ConfigRegistry) RegisterConfig(d *dao.Simple, key string, category ConfigCategory, valueType ConfigValueType,
	defaultValue []byte, minValue, maxValue []byte, description string, requiresVote bool) {

	entry := &ConfigEntry{
		Key:          key,
		Category:     category,
		ValueType:    valueType,
		CurrentValue: defaultValue,
		DefaultValue: defaultValue,
		MinValue:     minValue,
		MaxValue:     maxValue,
		Description:  description,
		RequiresVote: requiresVote,
	}
	cr.SetEntry(d, entry)
}

// ToStackItem converts ConfigEntry to stack item for RPC.
func (e *ConfigEntry) ToStackItem() stackitem.Item {
	return stackitem.NewArray([]stackitem.Item{
		stackitem.NewByteArray([]byte(e.Key)),
		stackitem.NewBigInteger(big.NewInt(int64(e.Category))),
		stackitem.NewBigInteger(big.NewInt(int64(e.ValueType))),
		stackitem.NewByteArray(e.CurrentValue),
		stackitem.NewByteArray(e.DefaultValue),
		stackitem.NewByteArray([]byte(e.Description)),
		stackitem.NewBool(e.RequiresVote),
		stackitem.NewBigInteger(big.NewInt(int64(e.LastModified))),
	})
}

// Helper methods for storage keys.
func (cr *ConfigRegistry) makeEntryKey(key string) []byte {
	result := make([]byte, 1+len(key))
	result[0] = configRegPrefixEntry
	copy(result[1:], key)
	return result
}

func (cr *ConfigRegistry) makeCategoryKey(category ConfigCategory, key string) []byte {
	result := make([]byte, 2+len(key))
	result[0] = configRegPrefixByCategory
	result[1] = byte(category)
	copy(result[2:], key)
	return result
}

// Serialization helpers.
func (cr *ConfigRegistry) serializeEntry(e *ConfigEntry) []byte {
	keyBytes := []byte(e.Key)
	descBytes := []byte(e.Description)

	size := 4 + len(keyBytes) + // key length + key
		1 + // category
		1 + // value type
		4 + len(e.CurrentValue) + // current value length + value
		4 + len(e.DefaultValue) + // default value length + value
		4 + len(e.MinValue) + // min value length + value
		4 + len(e.MaxValue) + // max value length + value
		4 + len(descBytes) + // description length + desc
		1 + // requires vote
		4 + // last modified
		20 // modified by

	data := make([]byte, size)
	offset := 0

	// Key
	binary.BigEndian.PutUint32(data[offset:], uint32(len(keyBytes)))
	offset += 4
	copy(data[offset:], keyBytes)
	offset += len(keyBytes)

	// Category
	data[offset] = byte(e.Category)
	offset++

	// Value type
	data[offset] = byte(e.ValueType)
	offset++

	// Current value
	binary.BigEndian.PutUint32(data[offset:], uint32(len(e.CurrentValue)))
	offset += 4
	copy(data[offset:], e.CurrentValue)
	offset += len(e.CurrentValue)

	// Default value
	binary.BigEndian.PutUint32(data[offset:], uint32(len(e.DefaultValue)))
	offset += 4
	copy(data[offset:], e.DefaultValue)
	offset += len(e.DefaultValue)

	// Min value
	binary.BigEndian.PutUint32(data[offset:], uint32(len(e.MinValue)))
	offset += 4
	copy(data[offset:], e.MinValue)
	offset += len(e.MinValue)

	// Max value
	binary.BigEndian.PutUint32(data[offset:], uint32(len(e.MaxValue)))
	offset += 4
	copy(data[offset:], e.MaxValue)
	offset += len(e.MaxValue)

	// Description
	binary.BigEndian.PutUint32(data[offset:], uint32(len(descBytes)))
	offset += 4
	copy(data[offset:], descBytes)
	offset += len(descBytes)

	// Requires vote
	if e.RequiresVote {
		data[offset] = 1
	}
	offset++

	// Last modified
	binary.BigEndian.PutUint32(data[offset:], e.LastModified)
	offset += 4

	// Modified by
	copy(data[offset:], e.ModifiedBy.BytesBE())

	return data
}

func (cr *ConfigRegistry) deserializeEntry(data []byte) *ConfigEntry {
	if len(data) < 10 {
		return nil
	}

	e := &ConfigEntry{}
	offset := 0

	// Key
	keyLen := binary.BigEndian.Uint32(data[offset:])
	offset += 4
	if offset+int(keyLen) > len(data) {
		return nil
	}
	e.Key = string(data[offset : offset+int(keyLen)])
	offset += int(keyLen)

	// Category
	e.Category = ConfigCategory(data[offset])
	offset++

	// Value type
	e.ValueType = ConfigValueType(data[offset])
	offset++

	// Current value
	if offset+4 > len(data) {
		return nil
	}
	valLen := binary.BigEndian.Uint32(data[offset:])
	offset += 4
	if offset+int(valLen) > len(data) {
		return nil
	}
	e.CurrentValue = make([]byte, valLen)
	copy(e.CurrentValue, data[offset:offset+int(valLen)])
	offset += int(valLen)

	// Default value
	if offset+4 > len(data) {
		return nil
	}
	defLen := binary.BigEndian.Uint32(data[offset:])
	offset += 4
	if offset+int(defLen) > len(data) {
		return nil
	}
	e.DefaultValue = make([]byte, defLen)
	copy(e.DefaultValue, data[offset:offset+int(defLen)])
	offset += int(defLen)

	// Min value
	if offset+4 > len(data) {
		return nil
	}
	minLen := binary.BigEndian.Uint32(data[offset:])
	offset += 4
	if offset+int(minLen) > len(data) {
		return nil
	}
	e.MinValue = make([]byte, minLen)
	copy(e.MinValue, data[offset:offset+int(minLen)])
	offset += int(minLen)

	// Max value
	if offset+4 > len(data) {
		return nil
	}
	maxLen := binary.BigEndian.Uint32(data[offset:])
	offset += 4
	if offset+int(maxLen) > len(data) {
		return nil
	}
	e.MaxValue = make([]byte, maxLen)
	copy(e.MaxValue, data[offset:offset+int(maxLen)])
	offset += int(maxLen)

	// Description
	if offset+4 > len(data) {
		return nil
	}
	descLen := binary.BigEndian.Uint32(data[offset:])
	offset += 4
	if offset+int(descLen) > len(data) {
		return nil
	}
	e.Description = string(data[offset : offset+int(descLen)])
	offset += int(descLen)

	// Requires vote
	if offset >= len(data) {
		return nil
	}
	e.RequiresVote = data[offset] == 1
	offset++

	// Last modified
	if offset+4 > len(data) {
		return nil
	}
	e.LastModified = binary.BigEndian.Uint32(data[offset:])
	offset += 4

	// Modified by
	if offset+20 > len(data) {
		return nil
	}
	e.ModifiedBy, _ = util.Uint160DecodeBytesBE(data[offset : offset+20])

	return e
}

// StandardConfigKeys defines standard configuration keys used across contracts.
var StandardConfigKeys = struct {
	// System
	MaxQueryLimit       string
	DefaultPageSize     string
	BlockInterval       string

	// Security
	RecoveryDelay       string
	RequiredApprovals   string
	RateLimitBlocks     string
	ProofExpiryBlocks   string

	// Economic
	TributeRateMild     string
	TributeRateSevere   string
	AsylumQuotaPerYear  string

	// Governance
	VotingAge           string
	ProposalQuorum      string
	ProposalThreshold   string
}{
	MaxQueryLimit:       "system:max_query_limit",
	DefaultPageSize:     "system:default_page_size",
	BlockInterval:       "system:block_interval",
	RecoveryDelay:       "security:recovery_delay",
	RequiredApprovals:   "security:required_approvals",
	RateLimitBlocks:     "security:rate_limit_blocks",
	ProofExpiryBlocks:   "security:proof_expiry_blocks",
	TributeRateMild:     "economic:tribute_rate_mild",
	TributeRateSevere:   "economic:tribute_rate_severe",
	AsylumQuotaPerYear:  "economic:asylum_quota_per_year",
	VotingAge:           "governance:voting_age",
	ProposalQuorum:      "governance:proposal_quorum",
	ProposalThreshold:   "governance:proposal_threshold",
}
