package genesis

import (
	"errors"
	"github.com/exchain/go-exchain/exchain/genesis"
	"math/big"
	"time"

	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/common/hexutil"
	"github.com/ethereum/go-ethereum/core"
	"github.com/ethereum/go-ethereum/core/types"
	"github.com/ethereum/go-ethereum/params"
)

// defaultGasLimit represents the default gas limit for a genesis block.
const defaultGasLimit = 30_000_000

// NewL2Genesis will create a new L2 genesis
func NewL2Genesis(config *DeployConfig, l1StartHeader *types.Header) (*genesis.GenesisBlock, error) {
	if config.L2ChainID == 0 {
		return nil, errors.New("must define L2 ChainID")
	}

	l1StartTime := l1StartHeader.Time
	difficulty := config.L2GenesisBlockDifficulty
	if difficulty == nil {
		difficulty = newHexBig(0)
	}

	blk := &genesis.GenesisBlock{
		ChainId:   config.L2ChainID,
		Timestamp: l1StartTime,
		AllocInfo: make(genesis.GenesisAlloc),
	}

	return blk, nil
}

// NewL1Genesis will create a new L1 genesis config
func NewL1Genesis(config *DeployConfig) (*core.Genesis, error) {
	if config.L1ChainID == 0 {
		return nil, errors.New("must define L1 ChainID")
	}

	chainConfig := params.ChainConfig{
		ChainID:             uint642Big(config.L1ChainID),
		HomesteadBlock:      big.NewInt(0),
		DAOForkBlock:        nil,
		DAOForkSupport:      false,
		EIP150Block:         big.NewInt(0),
		EIP155Block:         big.NewInt(0),
		EIP158Block:         big.NewInt(0),
		ByzantiumBlock:      big.NewInt(0),
		ConstantinopleBlock: big.NewInt(0),
		PetersburgBlock:     big.NewInt(0),
		IstanbulBlock:       big.NewInt(0),
		MuirGlacierBlock:    big.NewInt(0),
		BerlinBlock:         big.NewInt(0),
		LondonBlock:         big.NewInt(0),
		ArrowGlacierBlock:   big.NewInt(0),
		GrayGlacierBlock:    big.NewInt(0),
		ShanghaiTime:        u64ptr(0),
		CancunTime:          u64ptr(0),
		// To enable post-Merge consensus at genesis
		MergeNetsplitBlock:      big.NewInt(0),
		TerminalTotalDifficulty: big.NewInt(0),
	}

	gasLimit := config.L1GenesisBlockGasLimit
	if gasLimit == 0 {
		gasLimit = defaultGasLimit
	}
	baseFee := config.L1GenesisBlockBaseFeePerGas
	if baseFee == nil {
		baseFee = newHexBig(params.InitialBaseFee)
	}
	difficulty := config.L1GenesisBlockDifficulty
	if difficulty == nil {
		difficulty = newHexBig(0) // default to Merge-compatible difficulty value
	}
	timestamp := config.L1GenesisBlockTimestamp
	if timestamp == 0 {
		timestamp = hexutil.Uint64(time.Now().Unix())
	}
	if config.L1CancunTimeOffset != nil {
		cancunTime := uint64(timestamp) + uint64(*config.L1CancunTimeOffset)
		chainConfig.CancunTime = &cancunTime
	}

	return &core.Genesis{
		Config:        &chainConfig,
		Nonce:         uint64(config.L1GenesisBlockNonce),
		Timestamp:     uint64(timestamp),
		ExtraData:     make([]byte, 0),
		GasLimit:      uint64(gasLimit),
		Difficulty:    difficulty.ToInt(),
		Mixhash:       config.L1GenesisBlockMixHash,
		Coinbase:      config.L1GenesisBlockCoinbase,
		Number:        uint64(config.L1GenesisBlockNumber),
		GasUsed:       uint64(config.L1GenesisBlockGasUsed),
		ParentHash:    config.L1GenesisBlockParentHash,
		BaseFee:       baseFee.ToInt(),
		ExcessBlobGas: (*uint64)(config.L1GenesisBlockExcessBlobGas),
		BlobGasUsed:   (*uint64)(config.L1GenesisBlockBlobGasUsed),
		Alloc:         map[common.Address]types.Account{},
	}, nil
}

func u64ptr(n uint64) *uint64 {
	return &n
}
