package loadtest

import (
	cryptorand "crypto/rand"
	"crypto/sha256"
	_ "embed"
	"fmt"
	"math/rand"

	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/crypto/kzg4844"
	"github.com/ethereum/go-ethereum/params"

	"github.com/ethereum/go-ethereum/core/types"
)

type BlobCommitment struct {
	Blob          kzg4844.Blob
	Commitment    kzg4844.Commitment
	Proof         kzg4844.Proof
	VersionedHash common.Hash
}

// generateRandomBlobData will generate random data to be used for blob encoding
func generateRandomBlobData(size int) ([]byte, error) {
	data := make([]byte, size)
	n, err := cryptorand.Read(data)
	if err != nil {
		return nil, err
	}
	if n != size {
		return nil, fmt.Errorf("could not create random blob data with size %d: %v", size, err)
	}
	return data, nil
}

// createBlob takes in randomly generated byte slice and commits it with KZG
func createBlob(data []byte) kzg4844.Blob {
	blob := kzg4844.Blob{}
	fieldIndex := -1
	for i := 0; i < len(data); i += 31 {
		fieldIndex++
		if fieldIndex == params.BlobTxFieldElementsPerBlob {
			break
		}
		max := min(i+31, len(data))
		copy(blob[fieldIndex*32+1:], data[i:max])
	}
	return blob
}

// generateBlobCommitment will generate the values for BlobCommitment variables
func generateBlobCommitment(data []byte) (*BlobCommitment, error) {
	dataLen := len(data)
	if dataLen > params.BlobTxFieldElementsPerBlob*(params.BlobTxBytesPerFieldElement-1) {
		return nil, fmt.Errorf("blob data longer than allowed (length: %v, limit: %v)", dataLen, params.BlobTxFieldElementsPerBlob*(params.BlobTxBytesPerFieldElement-1))
	}
	blobCommitment := BlobCommitment{
		Blob: createBlob(data),
	}
	var err error

	// Generate blob commitment
	blobCommitment.Commitment, err = kzg4844.BlobToCommitment(&blobCommitment.Blob)
	if err != nil {
		return nil, fmt.Errorf("failed generating blob commitment: %w", err)
	}

	// Generate blob proof
	blobCommitment.Proof, err = kzg4844.ComputeBlobProof(&blobCommitment.Blob, blobCommitment.Commitment)
	if err != nil {
		return nil, fmt.Errorf("failed generating blob proof: %w", err)
	}

	// Build versioned hash
	blobCommitment.VersionedHash = sha256.Sum256(blobCommitment.Commitment[:])
	blobCommitment.VersionedHash[0] = 0x01
	return &blobCommitment, nil
}

// appendBlobCommitment will append the generated BlobCommitment values to blob transaction specific variables
func appendBlobCommitment(tx *types.BlobTx) error {
	var err error
	var blobBytes []byte
	var blobRefBytes []byte
	blobLen := rand.Intn((params.BlobTxFieldElementsPerBlob * (params.BlobTxBytesPerFieldElement - 1)) - len(blobBytes))
	blobRefBytes, _ = generateRandomBlobData(blobLen)

	if blobRefBytes == nil {
		return fmt.Errorf("unknown blob ref")
	}
	blobBytes = append(blobBytes, blobRefBytes...)

	blobCommitment, err := generateBlobCommitment(blobBytes)
	if err != nil {
		return fmt.Errorf("invalid blob: %w", err)
	}

	tx.BlobHashes = append(tx.BlobHashes, blobCommitment.VersionedHash)
	tx.Sidecar.Blobs = append(tx.Sidecar.Blobs, blobCommitment.Blob)
	tx.Sidecar.Commitments = append(tx.Sidecar.Commitments, blobCommitment.Commitment)
	tx.Sidecar.Proofs = append(tx.Sidecar.Proofs, blobCommitment.Proof)
	return nil
}
