// Copyright (c) 2017-2026 Lethean (https://lt.hn)
//
// Licensed under the European Union Public Licence (EUPL) version 1.2.
// SPDX-License-Identifier: EUPL-1.2

package p2p

import "dappco.re/go/core/p2p/node/levin"

// NewBlockNotification is NOTIFY_NEW_BLOCK (2001).
type NewBlockNotification struct {
	BlockBlob []byte   // Serialised block
	TxBlobs   [][]byte // Serialised transactions
	Height    uint64   // Current blockchain height
}

// Encode serialises the notification.
func (n *NewBlockNotification) Encode() ([]byte, error) {
	blockEntry := levin.Section{
		"block": levin.StringVal(n.BlockBlob),
		"txs":   levin.StringArrayVal(n.TxBlobs),
	}
	s := levin.Section{
		"b":                         levin.ObjectVal(blockEntry),
		"current_blockchain_height": levin.Uint64Val(n.Height),
	}
	return levin.EncodeStorage(s)
}

// Decode parses a new block notification from a storage blob.
func (n *NewBlockNotification) Decode(data []byte) error {
	s, err := levin.DecodeStorage(data)
	if err != nil {
		return err
	}
	if v, ok := s["current_blockchain_height"]; ok {
		n.Height, _ = v.AsUint64()
	}
	if v, ok := s["b"]; ok {
		obj, _ := v.AsSection()
		if blk, ok := obj["block"]; ok {
			n.BlockBlob, _ = blk.AsString()
		}
		if txs, ok := obj["txs"]; ok {
			n.TxBlobs, _ = txs.AsStringArray()
		}
	}
	return nil
}

// NewTransactionsNotification is NOTIFY_NEW_TRANSACTIONS (2002).
type NewTransactionsNotification struct {
	TxBlobs [][]byte
}

// Encode serialises the notification.
func (n *NewTransactionsNotification) Encode() ([]byte, error) {
	s := levin.Section{
		"txs": levin.StringArrayVal(n.TxBlobs),
	}
	return levin.EncodeStorage(s)
}

// Decode parses a new transactions notification.
func (n *NewTransactionsNotification) Decode(data []byte) error {
	s, err := levin.DecodeStorage(data)
	if err != nil {
		return err
	}
	if v, ok := s["txs"]; ok {
		n.TxBlobs, _ = v.AsStringArray()
	}
	return nil
}

// BlockCompleteEntry holds a block blob and its transaction blobs.
type BlockCompleteEntry struct {
	Block []byte   // Serialised block
	Txs   [][]byte // Serialised transactions
}

// RequestGetObjects is NOTIFY_REQUEST_GET_OBJECTS (2003).
type RequestGetObjects struct {
	Blocks [][]byte // 32-byte block hashes
	Txs    [][]byte // 32-byte tx hashes (usually empty for sync)
}

// Encode serialises the request.
// The C++ daemon uses KV_SERIALIZE_CONTAINER_POD_AS_BLOB for both blocks
// and txs, so we pack all hashes into single concatenated blobs.
func (r *RequestGetObjects) Encode() ([]byte, error) {
	blocksBlob := make([]byte, 0, len(r.Blocks)*32)
	for _, id := range r.Blocks {
		blocksBlob = append(blocksBlob, id...)
	}
	s := levin.Section{
		"blocks": levin.StringVal(blocksBlob),
	}
	if len(r.Txs) > 0 {
		txsBlob := make([]byte, 0, len(r.Txs)*32)
		for _, id := range r.Txs {
			txsBlob = append(txsBlob, id...)
		}
		s["txs"] = levin.StringVal(txsBlob)
	}
	return levin.EncodeStorage(s)
}

// Decode parses a get-objects request from a storage blob.
func (r *RequestGetObjects) Decode(data []byte) error {
	s, err := levin.DecodeStorage(data)
	if err != nil {
		return err
	}
	if v, ok := s["blocks"]; ok {
		blob, _ := v.AsString()
		r.Blocks = splitHashes(blob, 32)
	}
	if v, ok := s["txs"]; ok {
		blob, _ := v.AsString()
		r.Txs = splitHashes(blob, 32)
	}
	return nil
}

// ResponseGetObjects is NOTIFY_RESPONSE_GET_OBJECTS (2004).
type ResponseGetObjects struct {
	Blocks        []BlockCompleteEntry
	MissedIDs     [][]byte
	CurrentHeight uint64
}

// Encode serialises the response.
func (r *ResponseGetObjects) Encode() ([]byte, error) {
	sections := make([]levin.Section, len(r.Blocks))
	for i, entry := range r.Blocks {
		sections[i] = levin.Section{
			"block": levin.StringVal(entry.Block),
			"txs":   levin.StringArrayVal(entry.Txs),
		}
	}
	s := levin.Section{
		"blocks":                    levin.ObjectArrayVal(sections),
		"current_blockchain_height": levin.Uint64Val(r.CurrentHeight),
	}
	if len(r.MissedIDs) > 0 {
		// missed_ids uses KV_SERIALIZE_CONTAINER_POD_AS_BLOB in C++.
		blob := make([]byte, 0, len(r.MissedIDs)*32)
		for _, id := range r.MissedIDs {
			blob = append(blob, id...)
		}
		s["missed_ids"] = levin.StringVal(blob)
	}
	return levin.EncodeStorage(s)
}

// Decode parses a get-objects response from a storage blob.
func (r *ResponseGetObjects) Decode(data []byte) error {
	s, err := levin.DecodeStorage(data)
	if err != nil {
		return err
	}
	if v, ok := s["current_blockchain_height"]; ok {
		r.CurrentHeight, _ = v.AsUint64()
	}
	if v, ok := s["blocks"]; ok {
		sections, _ := v.AsSectionArray()
		r.Blocks = make([]BlockCompleteEntry, len(sections))
		for i, sec := range sections {
			if blk, ok := sec["block"]; ok {
				r.Blocks[i].Block, _ = blk.AsString()
			}
			if txs, ok := sec["txs"]; ok {
				r.Blocks[i].Txs, _ = txs.AsStringArray()
			}
		}
	}
	if v, ok := s["missed_ids"]; ok {
		// missed_ids uses KV_SERIALIZE_CONTAINER_POD_AS_BLOB in C++.
		blob, _ := v.AsString()
		r.MissedIDs = splitHashes(blob, 32)
	}
	return nil
}

// RequestChain is NOTIFY_REQUEST_CHAIN (2006).
type RequestChain struct {
	BlockIDs [][]byte // Array of 32-byte block hashes
}

// Encode serialises the request.
// The C++ daemon uses KV_SERIALIZE_CONTAINER_POD_AS_BLOB for block_ids,
// so we pack all hashes into a single concatenated blob.
func (r *RequestChain) Encode() ([]byte, error) {
	blob := make([]byte, 0, len(r.BlockIDs)*32)
	for _, id := range r.BlockIDs {
		blob = append(blob, id...)
	}
	s := levin.Section{
		"block_ids": levin.StringVal(blob),
	}
	return levin.EncodeStorage(s)
}

// BlockContextInfo holds a block hash and cumulative size from a chain
// entry response. Mirrors the C++ block_context_info struct.
type BlockContextInfo struct {
	Hash      []byte // 32-byte block hash (KV_SERIALIZE_VAL_POD_AS_BLOB)
	CumulSize uint64 // Cumulative block size
}

// ResponseChainEntry is NOTIFY_RESPONSE_CHAIN_ENTRY (2007).
type ResponseChainEntry struct {
	StartHeight uint64
	TotalHeight uint64
	BlockIDs    [][]byte           // Convenience: just the hashes
	Blocks      []BlockContextInfo // Full entries with cumulative sizes
}

// Decode parses a chain entry response.
// m_block_ids is an object array of block_context_info, each with
// "h" (hash blob) and "cumul_size" (uint64).
func (r *ResponseChainEntry) Decode(data []byte) error {
	s, err := levin.DecodeStorage(data)
	if err != nil {
		return err
	}
	if v, ok := s["start_height"]; ok {
		r.StartHeight, _ = v.AsUint64()
	}
	if v, ok := s["total_height"]; ok {
		r.TotalHeight, _ = v.AsUint64()
	}
	if v, ok := s["m_block_ids"]; ok {
		sections, _ := v.AsSectionArray()
		r.Blocks = make([]BlockContextInfo, len(sections))
		r.BlockIDs = make([][]byte, len(sections))
		for i, sec := range sections {
			if hv, ok := sec["h"]; ok {
				r.Blocks[i].Hash, _ = hv.AsString()
				r.BlockIDs[i] = r.Blocks[i].Hash
			}
			if cv, ok := sec["cumul_size"]; ok {
				r.Blocks[i].CumulSize, _ = cv.AsUint64()
			}
		}
	}
	return nil
}

// splitHashes divides a concatenated blob into fixed-size hash slices.
func splitHashes(blob []byte, size int) [][]byte {
	n := len(blob) / size
	out := make([][]byte, n)
	for i := range n {
		out[i] = blob[i*size : (i+1)*size]
	}
	return out
}
