package boot

import (
	"encoding/binary"
	"encoding/json"
	"fmt"
	"testing"

	"github.com/ethereum-optimism/optimism/op-node/chaincfg"
	preimage "github.com/ethereum-optimism/optimism/op-preimage"
	"github.com/ethereum-optimism/optimism/op-program/chainconfig"
	"github.com/ethereum-optimism/optimism/op-service/eth"
	"github.com/ethereum/go-ethereum/common"
	"github.com/stretchr/testify/require"
)

func TestBootstrapClient(t *testing.T) {
	rollupCfg := chaincfg.OPSepolia()
	bootInfo := &BootInfo{
		L1Head:             common.HexToHash("0x1111"),
		L2OutputRoot:       common.HexToHash("0x2222"),
		L2Claim:            common.HexToHash("0x3333"),
		L2ClaimBlockNumber: 1,
		L2ChainID:          eth.ChainIDFromBig(rollupCfg.L2ChainID),
		L2ChainConfig:      chainconfig.OPSepoliaChainConfig(),
		RollupConfig:       rollupCfg,
	}
	mockOracle := newMockPreinteropBootstrapOracle(bootInfo, false)
	readBootInfo := NewBootstrapClient(mockOracle).BootInfo()
	require.EqualValues(t, bootInfo, readBootInfo)
}

func TestBootstrapClient_CustomChain(t *testing.T) {
	bootInfo := &BootInfo{
		L1Head:             common.HexToHash("0x1111"),
		L2OutputRoot:       common.HexToHash("0x2222"),
		L2Claim:            common.HexToHash("0x3333"),
		L2ClaimBlockNumber: 1,
		L2ChainID:          CustomChainIDIndicator,
		L2ChainConfig:      chainconfig.OPSepoliaChainConfig(),
		RollupConfig:       chaincfg.OPSepolia(),
	}
	mockOracle := newMockPreinteropBootstrapOracle(bootInfo, true)
	readBootInfo := NewBootstrapClient(mockOracle).BootInfo()
	require.EqualValues(t, bootInfo, readBootInfo)
}

func TestBootstrapClient_UnknownChainPanics(t *testing.T) {
	bootInfo := &BootInfo{
		L1Head:             common.HexToHash("0x1111"),
		L2OutputRoot:       common.HexToHash("0x2222"),
		L2Claim:            common.HexToHash("0x3333"),
		L2ClaimBlockNumber: 1,
		L2ChainID:          eth.ChainID{0xdead},
	}
	mockOracle := newMockPreinteropBootstrapOracle(bootInfo, false)
	client := NewBootstrapClient(mockOracle)
	require.Panics(t, func() { client.BootInfo() })
}

func newMockPreinteropBootstrapOracle(info *BootInfo, custom bool) *mockPreinteropBoostrapOracle {
	return &mockPreinteropBoostrapOracle{
		mockBoostrapOracle: mockBoostrapOracle{
			l1Head:             info.L1Head,
			l2OutputRoot:       info.L2OutputRoot,
			l2Claim:            info.L2Claim,
			l2ClaimBlockNumber: info.L2ClaimBlockNumber,
		},
		b:      info,
		custom: custom,
	}
}

type mockPreinteropBoostrapOracle struct {
	mockBoostrapOracle
	b      *BootInfo
	custom bool
}

func (o *mockPreinteropBoostrapOracle) Get(key preimage.Key) []byte {
	switch key.PreimageKey() {
	case L2ChainIDLocalIndex.PreimageKey():
		return binary.BigEndian.AppendUint64(nil, eth.EvilChainIDToUInt64(o.b.L2ChainID))
	case L2ChainConfigLocalIndex.PreimageKey():
		if !o.custom {
			panic(fmt.Sprintf("unexpected oracle request for preimage key %x", key.PreimageKey()))
		}
		b, _ := json.Marshal(o.b.L2ChainConfig)
		return b
	case RollupConfigLocalIndex.PreimageKey():
		if !o.custom {
			panic(fmt.Sprintf("unexpected oracle request for preimage key %x", key.PreimageKey()))
		}
		b, _ := json.Marshal(o.b.RollupConfig)
		return b
	default:
		return o.mockBoostrapOracle.Get(key)
	}
}