// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package block

import (
	"errors"
	"fmt"
	"time"

	"github.com/luxfi/ids"
	"github.com/luxfi/node/staking"
	"github.com/luxfi/crypto/hash"
	"github.com/luxfi/codec/wrappers"
)

var (
	_ SignedBlock = (*statelessBlock)(nil)

	errUnexpectedSignature = errors.New("signature provided when none was expected")
	errInvalidCertificate  = errors.New("invalid certificate")
)

// Epoch represents a P-Chain epoch for validator set coordination
type Epoch struct {
	PChainHeight uint64 `serialize:"true" json:"pChainHeight"`
	Number       uint64 `serialize:"true" json:"number"`
	StartTime    int64  `serialize:"true" json:"startTime"`
}

type Block interface {
	ID() ids.ID
	ParentID() ids.ID
	Block() []byte
	Bytes() []byte

	initialize(bytes []byte) error
	verify(chainID ids.ID) error
}

type SignedBlock interface {
	Block

	PChainHeight() uint64
	PChainEpoch() Epoch
	Timestamp() time.Time

	// Proposer returns the ID of the node that proposed this block. If no node
	// signed this block, [ids.EmptyNodeID] will be returned.
	Proposer() ids.NodeID

	// Data Availability fields (v1.1 spec)
	DARoot() [32]byte          // Root of DA commitments
	WitnessRoot() [32]byte     // Root of witnesses/proofs
	MessagesOutRoot() [32]byte // Root of outgoing cross-chain messages
	BlobCount() uint32         // Number of DA blobs in block
}

type statelessUnsignedBlock struct {
	ParentID     ids.ID `serialize:"true"`
	Timestamp    int64  `serialize:"true"`
	PChainHeight uint64 `serialize:"true"`
	Epoch        Epoch  `serialize:"true"`
	Certificate  []byte `serialize:"true"`
	Block        []byte `serialize:"true"`

	// Data Availability fields (v1.1 spec)
	DARoot          [32]byte `serialize:"true"` // Root of DA commitments
	WitnessRoot     [32]byte `serialize:"true"` // Root of witnesses/proofs
	MessagesOutRoot [32]byte `serialize:"true"` // Root of outgoing cross-chain messages
	BlobCount       uint32   `serialize:"true"` // Number of DA blobs in block
}

type statelessBlock struct {
	StatelessBlock statelessUnsignedBlock `serialize:"true"`
	Signature      []byte                 `serialize:"true"`

	id        ids.ID
	timestamp time.Time
	cert      *staking.Certificate
	proposer  ids.NodeID
	bytes     []byte
}

func (b *statelessBlock) ID() ids.ID {
	return b.id
}

func (b *statelessBlock) ParentID() ids.ID {
	return b.StatelessBlock.ParentID
}

func (b *statelessBlock) Block() []byte {
	return b.StatelessBlock.Block
}

func (b *statelessBlock) Bytes() []byte {
	return b.bytes
}

func (b *statelessBlock) initialize(bytes []byte) error {
	b.bytes = bytes

	// The serialized form of the block is the unsignedBytes followed by the
	// signature, which is prefixed by a uint32. So, we need to strip off the
	// signature as well as it's length prefix to get the unsigned bytes.
	lenUnsignedBytes := len(bytes) - wrappers.IntLen - len(b.Signature)
	unsignedBytes := bytes[:lenUnsignedBytes]
	b.id = hash.ComputeHash256Array(unsignedBytes)

	b.timestamp = time.Unix(b.StatelessBlock.Timestamp, 0)
	if len(b.StatelessBlock.Certificate) == 0 {
		return nil
	}

	var err error
	b.cert, err = staking.ParseCertificate(b.StatelessBlock.Certificate)
	if err != nil {
		return fmt.Errorf("%w: %w", errInvalidCertificate, err)
	}

	b.proposer = ids.NodeIDFromCert(&ids.Certificate{
		Raw:       b.cert.Raw,
		PublicKey: b.cert.PublicKey,
	})
	return nil
}

func (b *statelessBlock) verify(chainID ids.ID) error {
	if len(b.StatelessBlock.Certificate) == 0 {
		if len(b.Signature) > 0 {
			return errUnexpectedSignature
		}
		return nil
	}

	header, err := BuildHeader(chainID, b.StatelessBlock.ParentID, b.id)
	if err != nil {
		return err
	}

	headerBytes := header.Bytes()
	return staking.CheckSignature(
		b.cert,
		headerBytes,
		b.Signature,
	)
}

func (b *statelessBlock) PChainHeight() uint64 {
	return b.StatelessBlock.PChainHeight
}

func (b *statelessBlock) PChainEpoch() Epoch {
	return b.StatelessBlock.Epoch
}

func (b *statelessBlock) Timestamp() time.Time {
	return b.timestamp
}

func (b *statelessBlock) Proposer() ids.NodeID {
	return b.proposer
}

func (b *statelessBlock) DARoot() [32]byte {
	return b.StatelessBlock.DARoot
}

func (b *statelessBlock) WitnessRoot() [32]byte {
	return b.StatelessBlock.WitnessRoot
}

func (b *statelessBlock) MessagesOutRoot() [32]byte {
	return b.StatelessBlock.MessagesOutRoot
}

func (b *statelessBlock) BlobCount() uint32 {
	return b.StatelessBlock.BlobCount
}
