package derive

import (
	"fmt"
	"github.com/ethereum/go-ethereum/common"
	nebulav1 "github.com/exchain/go-exchain/exchain/protocol/gen/go/nebula/v1"
	"github.com/exchain/go-exchain/op-node/rollup"
	"github.com/exchain/go-exchain/op-service/eth"
	log "github.com/sirupsen/logrus"
)

// PayloadToBlockRef extracts the essential L2BlockRef information from an execution payload,
// falling back to genesis information if necessary.
func PayloadToBlockRef(rollupCfg *rollup.Config, payload *eth.ExecutionPayload) (eth.L2BlockRef, error) {
	genesis := &rollupCfg.Genesis
	var l1Origin eth.BlockID
	var sequenceNumber uint64
	if uint64(payload.BlockNumber) == genesis.L2.Number {
		if payload.BlockHash != genesis.L2.Hash {
			return eth.L2BlockRef{}, fmt.Errorf("expected L2 genesis hash to match L2 block at genesis block number %d: %s <> %s", genesis.L2.Number, payload.BlockHash, genesis.L2.Hash)
		}
		l1Origin = genesis.L1
		sequenceNumber = 0
		log.WithFields(log.Fields{
			"blockNumber": payload.BlockNumber,
			"blockHash":   payload.BlockHash,
			"l1info":      l1Origin,
		}).Debug("using genesis L1 info for L2 block")
	} else {
		header := payload.Payload.Header
		l1Origin = eth.BlockID{Hash: common.BytesToHash(header.L1Hash), Number: header.L1Height}
		sequenceNumber = header.SequenceNumber
	}

	return eth.L2BlockRef{
		Hash:           payload.BlockHash,
		Number:         uint64(payload.BlockNumber),
		ParentHash:     payload.ParentHash,
		Time:           uint64(payload.Timestamp),
		L1Origin:       l1Origin,
		SequenceNumber: sequenceNumber,
	}, nil
}

func PayloadToSystemConfig(rollupCfg *rollup.Config, payload *eth.ExecutionPayload) (eth.SystemConfig, error) {
	if uint64(payload.BlockNumber) == rollupCfg.Genesis.L2.Number {
		if payload.BlockHash != rollupCfg.Genesis.L2.Hash {
			return eth.SystemConfig{}, fmt.Errorf(
				"expected L2 genesis hash to match L2 block at genesis block number %d: %s <> %s",
				rollupCfg.Genesis.L2.Number, payload.BlockHash, rollupCfg.Genesis.L2.Hash)
		}
		return rollupCfg.Genesis.SystemConfig, nil
	}
	txs := payload.Transactions()
	if len(txs) == 0 {
		return eth.SystemConfig{}, fmt.Errorf("l2 block is missing L1 info, block hash: %s", payload.BlockHash)
	}
	tx := txs[0]
	if tx.TxType != nebulav1.TxType_ProtocolTx {
		return eth.SystemConfig{}, fmt.Errorf("first payload tx has unexpected tx type: %d", tx.TxType)
	}
	content := tx.GetProtocolTx()
	if content == nil {
		return eth.SystemConfig{}, fmt.Errorf("first payload tx has unexpected tx type: %d", tx.TxType)
	}
	info, err := L1BlockInfoFromNebula(rollupCfg, uint64(payload.Timestamp), content)
	if err != nil {
		return eth.SystemConfig{}, fmt.Errorf("failed to parse L1 info deposit tx from L2 block: %w", err)
	}
	r := eth.SystemConfig{
		BatcherAddr: info.BatcherAddr,
		Overhead:    info.L1FeeOverhead,
		Scalar:      info.L1FeeScalar,
	}

	return r, nil
}
