package native

import (
	"encoding/binary"

	"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/crypto/hash"
	"git.marketally.com/tutus-one/tutus-chain/pkg/util"
)

// LOW-001: Event archival system for managing event log growth.

type EventArchivalConfig struct {
	RetentionBlocks    uint32
	ArchiveEnabled     bool
	CommitmentInterval uint32
}

func DefaultEventArchivalConfig() *EventArchivalConfig {
	return &EventArchivalConfig{
		RetentionBlocks:    2592000,
		ArchiveEnabled:     false,
		CommitmentInterval: 8640,
	}
}

type EventArchivalState struct {
	LastArchivedBlock   uint32
	LastCommitmentBlock uint32
	TotalEventsArchived uint64
	TotalCommitments    uint64
}

type EventCommitment struct {
	CommitmentID uint64
	StartBlock   uint32
	EndBlock     uint32
	MerkleRoot   util.Uint256
	EventCount   uint64
	CreatedAt    uint32
}

const (
	eventArchivalPrefixConfig     byte = 0xE0
	eventArchivalPrefixState      byte = 0xE1
	eventArchivalPrefixCommitment byte = 0xE2
	eventArchivalPrefixByBlock    byte = 0xE3
)

type EventArchiver struct {
	contractID int32
}

func NewEventArchiver(contractID int32) *EventArchiver {
	return &EventArchiver{contractID: contractID}
}

func (ea *EventArchiver) GetConfig(d *dao.Simple) *EventArchivalConfig {
	key := []byte{eventArchivalPrefixConfig}
	si := d.GetStorageItem(ea.contractID, key)
	if si == nil {
		return DefaultEventArchivalConfig()
	}
	cfg := &EventArchivalConfig{}
	cfg.RetentionBlocks = binary.BigEndian.Uint32(si[:4])
	cfg.ArchiveEnabled = si[4] == 1
	cfg.CommitmentInterval = binary.BigEndian.Uint32(si[5:9])
	return cfg
}

func (ea *EventArchiver) PutConfig(d *dao.Simple, cfg *EventArchivalConfig) {
	key := []byte{eventArchivalPrefixConfig}
	data := make([]byte, 9)
	binary.BigEndian.PutUint32(data[:4], cfg.RetentionBlocks)
	if cfg.ArchiveEnabled {
		data[4] = 1
	}
	binary.BigEndian.PutUint32(data[5:9], cfg.CommitmentInterval)
	d.PutStorageItem(ea.contractID, key, data)
}

func (ea *EventArchiver) GetState(d *dao.Simple) *EventArchivalState {
	key := []byte{eventArchivalPrefixState}
	si := d.GetStorageItem(ea.contractID, key)
	if si == nil {
		return &EventArchivalState{}
	}
	st := &EventArchivalState{}
	st.LastArchivedBlock = binary.BigEndian.Uint32(si[:4])
	st.LastCommitmentBlock = binary.BigEndian.Uint32(si[4:8])
	st.TotalEventsArchived = binary.BigEndian.Uint64(si[8:16])
	st.TotalCommitments = binary.BigEndian.Uint64(si[16:24])
	return st
}

func (ea *EventArchiver) PutState(d *dao.Simple, st *EventArchivalState) {
	key := []byte{eventArchivalPrefixState}
	data := make([]byte, 24)
	binary.BigEndian.PutUint32(data[:4], st.LastArchivedBlock)
	binary.BigEndian.PutUint32(data[4:8], st.LastCommitmentBlock)
	binary.BigEndian.PutUint64(data[8:16], st.TotalEventsArchived)
	binary.BigEndian.PutUint64(data[16:24], st.TotalCommitments)
	d.PutStorageItem(ea.contractID, key, data)
}

func (ea *EventArchiver) CreateCommitment(d *dao.Simple, startBlock, endBlock uint32, events [][]byte) *EventCommitment {
	st := ea.GetState(d)
	merkleRoot := computeEventsMerkleRoot(events)
	commitment := &EventCommitment{
		CommitmentID: st.TotalCommitments + 1,
		StartBlock:   startBlock,
		EndBlock:     endBlock,
		MerkleRoot:   merkleRoot,
		EventCount:   uint64(len(events)),
		CreatedAt:    endBlock,
	}
	ea.putCommitment(d, commitment)
	st.LastCommitmentBlock = endBlock
	st.TotalCommitments++
	st.TotalEventsArchived += uint64(len(events))
	ea.PutState(d, st)
	return commitment
}

func (ea *EventArchiver) GetCommitment(d *dao.Simple, commitmentID uint64) *EventCommitment {
	key := make([]byte, 9)
	key[0] = eventArchivalPrefixCommitment
	binary.BigEndian.PutUint64(key[1:], commitmentID)
	si := d.GetStorageItem(ea.contractID, key)
	if si == nil {
		return nil
	}
	c := &EventCommitment{}
	c.CommitmentID = binary.BigEndian.Uint64(si[:8])
	c.StartBlock = binary.BigEndian.Uint32(si[8:12])
	c.EndBlock = binary.BigEndian.Uint32(si[12:16])
	copy(c.MerkleRoot[:], si[16:48])
	c.EventCount = binary.BigEndian.Uint64(si[48:56])
	c.CreatedAt = binary.BigEndian.Uint32(si[56:60])
	return c
}

func (ea *EventArchiver) putCommitment(d *dao.Simple, c *EventCommitment) {
	key := make([]byte, 9)
	key[0] = eventArchivalPrefixCommitment
	binary.BigEndian.PutUint64(key[1:], c.CommitmentID)
	data := make([]byte, 60)
	binary.BigEndian.PutUint64(data[:8], c.CommitmentID)
	binary.BigEndian.PutUint32(data[8:12], c.StartBlock)
	binary.BigEndian.PutUint32(data[12:16], c.EndBlock)
	copy(data[16:48], c.MerkleRoot[:])
	binary.BigEndian.PutUint64(data[48:56], c.EventCount)
	binary.BigEndian.PutUint32(data[56:60], c.CreatedAt)
	d.PutStorageItem(ea.contractID, key, data)
	blockKey := make([]byte, 5)
	blockKey[0] = eventArchivalPrefixByBlock
	binary.BigEndian.PutUint32(blockKey[1:], c.EndBlock)
	binary.BigEndian.PutUint64(data[:8], c.CommitmentID)
	d.PutStorageItem(ea.contractID, blockKey, data[:8])
}

func (ea *EventArchiver) GetCommitmentForBlock(d *dao.Simple, blockHeight uint32) *EventCommitment {
	var foundID uint64
	prefix := []byte{eventArchivalPrefixByBlock}
	d.Seek(ea.contractID, storage.SeekRange{Prefix: prefix}, func(k, v []byte) bool {
		if len(k) >= 4 && len(v) >= 8 {
			endBlock := binary.BigEndian.Uint32(k[:4])
			if endBlock >= blockHeight {
				foundID = binary.BigEndian.Uint64(v[:8])
				return false
			}
		}
		return true
	})
	if foundID == 0 {
		return nil
	}
	return ea.GetCommitment(d, foundID)
}

func (ea *EventArchiver) ShouldCreateCommitment(d *dao.Simple, currentBlock uint32) bool {
	cfg := ea.GetConfig(d)
	if !cfg.ArchiveEnabled {
		return false
	}
	st := ea.GetState(d)
	return currentBlock >= st.LastCommitmentBlock+cfg.CommitmentInterval
}

func (ea *EventArchiver) CanPruneEvents(d *dao.Simple, blockHeight, currentBlock uint32) bool {
	cfg := ea.GetConfig(d)
	if !cfg.ArchiveEnabled {
		return false
	}
	if currentBlock < blockHeight+cfg.RetentionBlocks {
		return false
	}
	commitment := ea.GetCommitmentForBlock(d, blockHeight)
	return commitment != nil
}

func computeEventsMerkleRoot(events [][]byte) util.Uint256 {
	if len(events) == 0 {
		return util.Uint256{}
	}
	hashes := make([]util.Uint256, len(events))
	for i, event := range events {
		hashes[i] = hash.Sha256(event)
	}
	for len(hashes) > 1 {
		if len(hashes)%2 == 1 {
			hashes = append(hashes, hashes[len(hashes)-1])
		}
		newHashes := make([]util.Uint256, len(hashes)/2)
		for i := 0; i < len(hashes); i += 2 {
			combined := make([]byte, 64)
			copy(combined[:32], hashes[i][:])
			copy(combined[32:], hashes[i+1][:])
			newHashes[i/2] = hash.Sha256(combined)
		}
		hashes = newHashes
	}
	return hashes[0]
}

func VerifyEventInCommitment(eventData []byte, merkleProof []util.Uint256, index int, merkleRoot util.Uint256) bool {
	h := hash.Sha256(eventData)
	for _, proofElement := range merkleProof {
		var combined []byte
		if index%2 == 0 {
			combined = append(h[:], proofElement[:]...)
		} else {
			combined = append(proofElement[:], h[:]...)
		}
		h = hash.Sha256(combined)
		index /= 2
	}
	return h == merkleRoot
}
