// 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 (
	"encoding/binary"
	"fmt"

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

// PeerlistEntrySize is the packed size of a peerlist entry (ip + port + id + last_seen).
const PeerlistEntrySize = 24

// NodeData contains the node identity exchanged during handshake.
type NodeData struct {
	NetworkID [16]byte
	PeerID    uint64
	LocalTime int64
	MyPort    uint32
}

// MarshalSection encodes NodeData into a portable storage Section.
func (n *NodeData) MarshalSection() levin.Section {
	return levin.Section{
		"network_id": levin.StringVal(n.NetworkID[:]),
		"peer_id":    levin.Uint64Val(n.PeerID),
		"local_time": levin.Int64Val(n.LocalTime),
		"my_port":    levin.Uint32Val(n.MyPort),
	}
}

// UnmarshalSection decodes NodeData from a portable storage Section.
func (n *NodeData) UnmarshalSection(s levin.Section) error {
	if v, ok := s["network_id"]; ok {
		blob, err := v.AsString()
		if err != nil {
			return err
		}
		if len(blob) >= 16 {
			copy(n.NetworkID[:], blob[:16])
		}
	}
	if v, ok := s["peer_id"]; ok {
		val, err := v.AsUint64()
		if err != nil {
			return err
		}
		n.PeerID = val
	}
	if v, ok := s["local_time"]; ok {
		val, err := v.AsInt64()
		if err != nil {
			return err
		}
		n.LocalTime = val
	}
	if v, ok := s["my_port"]; ok {
		val, err := v.AsUint32()
		if err != nil {
			return err
		}
		n.MyPort = val
	}
	return nil
}

// PeerlistEntry is a decoded peerlist entry from a handshake response.
type PeerlistEntry struct {
	IP       uint32
	Port     uint32
	ID       uint64
	LastSeen int64
}

// DecodePeerlist splits a packed peerlist blob into entries.
func DecodePeerlist(blob []byte) []PeerlistEntry {
	n := len(blob) / PeerlistEntrySize
	entries := make([]PeerlistEntry, n)
	for i := range n {
		off := i * PeerlistEntrySize
		entries[i] = PeerlistEntry{
			IP:       binary.LittleEndian.Uint32(blob[off : off+4]),
			Port:     binary.LittleEndian.Uint32(blob[off+4 : off+8]),
			ID:       binary.LittleEndian.Uint64(blob[off+8 : off+16]),
			LastSeen: int64(binary.LittleEndian.Uint64(blob[off+16 : off+24])),
		}
	}
	return entries
}

// HandshakeRequest is a COMMAND_HANDSHAKE request.
type HandshakeRequest struct {
	NodeData    NodeData
	PayloadData CoreSyncData
}

// MarshalSection encodes the request.
func (r *HandshakeRequest) MarshalSection() levin.Section {
	return levin.Section{
		"node_data":    levin.ObjectVal(r.NodeData.MarshalSection()),
		"payload_data": levin.ObjectVal(r.PayloadData.MarshalSection()),
	}
}

// UnmarshalSection decodes the request.
func (r *HandshakeRequest) UnmarshalSection(s levin.Section) error {
	if v, ok := s["node_data"]; ok {
		obj, err := v.AsSection()
		if err != nil {
			return err
		}
		if err := r.NodeData.UnmarshalSection(obj); err != nil {
			return err
		}
	}
	if v, ok := s["payload_data"]; ok {
		obj, err := v.AsSection()
		if err != nil {
			return err
		}
		if err := r.PayloadData.UnmarshalSection(obj); err != nil {
			return err
		}
	}
	return nil
}

// EncodeHandshakeRequest serialises a handshake request into a storage blob.
func EncodeHandshakeRequest(req *HandshakeRequest) ([]byte, error) {
	return levin.EncodeStorage(req.MarshalSection())
}

// HandshakeResponse is a COMMAND_HANDSHAKE response.
type HandshakeResponse struct {
	NodeData     NodeData
	PayloadData  CoreSyncData
	PeerlistBlob []byte // Raw packed peerlist (24 bytes per entry)
}

// Decode parses a handshake response from a storage blob.
func (r *HandshakeResponse) Decode(data []byte) error {
	s, err := levin.DecodeStorage(data)
	if err != nil {
		return err
	}
	if v, ok := s["node_data"]; ok {
		obj, err := v.AsSection()
		if err != nil {
			return err
		}
		if err := r.NodeData.UnmarshalSection(obj); err != nil {
			return err
		}
	}
	if v, ok := s["payload_data"]; ok {
		obj, err := v.AsSection()
		if err != nil {
			return err
		}
		if err := r.PayloadData.UnmarshalSection(obj); err != nil {
			return err
		}
	}
	if v, ok := s["local_peerlist"]; ok {
		blob, err := v.AsString()
		if err != nil {
			return err
		}
		r.PeerlistBlob = blob
	}
	return nil
}

// ValidateHandshakeResponse verifies that a remote peer's handshake response
// matches the expected network and satisfies the minimum build version gate.
//
// Example:
//
//	err := ValidateHandshakeResponse(&resp, config.NetworkIDMainnet, false)
func ValidateHandshakeResponse(resp *HandshakeResponse, expectedNetworkID [16]byte, isTestnet bool) error {
	if resp.NodeData.NetworkID != expectedNetworkID {
		return fmt.Errorf("p2p: peer network id %x does not match expected %x",
			resp.NodeData.NetworkID, expectedNetworkID)
	}

	buildVersion, ok := PeerBuildVersion(resp.PayloadData.ClientVersion)
	if !ok {
		return fmt.Errorf("p2p: peer build %q is malformed", resp.PayloadData.ClientVersion)
	}

	if !MeetsMinimumBuildVersion(resp.PayloadData.ClientVersion, isTestnet) {
		minBuild := MinimumRequiredBuildVersion(isTestnet)
		return fmt.Errorf("p2p: peer build %q parsed as %d below minimum %d",
			resp.PayloadData.ClientVersion, buildVersion, minBuild)
	}

	return nil
}
