package cannon

import (
	"context"
	"os"
	"path/filepath"
	"testing"

	"github.com/ethereum/go-ethereum/common"
	"github.com/stretchr/testify/require"

	"github.com/ethereum-optimism/optimism/cannon/mipsevm"
	"github.com/ethereum-optimism/optimism/cannon/mipsevm/memory"
	"github.com/ethereum-optimism/optimism/cannon/mipsevm/singlethreaded"
)

func newCannonPrestateProvider(dataDir string, prestate string) *CannonPrestateProvider {
	return &CannonPrestateProvider{
		prestate: filepath.Join(dataDir, prestate),
	}
}

func TestAbsolutePreStateCommitment(t *testing.T) {
	dataDir := t.TempDir()

	prestate := "state.json"

	t.Run("StateUnavailable", func(t *testing.T) {
		provider := newCannonPrestateProvider("/dir/does/not/exist", prestate)
		_, err := provider.AbsolutePreStateCommitment(context.Background())
		require.ErrorIs(t, err, os.ErrNotExist)
	})

	t.Run("InvalidStateFile", func(t *testing.T) {
		setupPreState(t, dataDir, "invalid.json")
		provider := newCannonPrestateProvider(dataDir, prestate)
		_, err := provider.AbsolutePreStateCommitment(context.Background())
		require.ErrorContains(t, err, "invalid mipsevm state")
	})

	t.Run("ExpectedAbsolutePreState", func(t *testing.T) {
		setupPreState(t, dataDir, "state.json")
		provider := newCannonPrestateProvider(dataDir, prestate)
		actual, err := provider.AbsolutePreStateCommitment(context.Background())
		require.NoError(t, err)
		state := singlethreaded.State{
			Memory:         memory.NewMemory(),
			PreimageKey:    common.HexToHash("cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"),
			PreimageOffset: 0,
			Cpu: mipsevm.CpuScalars{
				PC:     0,
				NextPC: 1,
				LO:     0,
				HI:     0,
			},
			Heap:      0,
			ExitCode:  0,
			Exited:    false,
			Step:      0,
			Registers: [32]uint32{},
		}
		_, expected := state.EncodeWitness()
		require.Equal(t, expected, actual)
	})

	t.Run("CacheAbsolutePreState", func(t *testing.T) {
		setupPreState(t, dataDir, prestate)
		provider := newCannonPrestateProvider(dataDir, prestate)
		first, err := provider.AbsolutePreStateCommitment(context.Background())
		require.NoError(t, err)

		// Remove the prestate from disk
		require.NoError(t, os.Remove(provider.prestate))

		// Value should still be available from cache
		cached, err := provider.AbsolutePreStateCommitment(context.Background())
		require.NoError(t, err)
		require.Equal(t, first, cached)
	})
}

func setupPreState(t *testing.T, dataDir string, filename string) {
	srcDir := filepath.Join("test_data")
	path := filepath.Join(srcDir, filename)
	file, err := testData.ReadFile(path)
	require.NoErrorf(t, err, "reading %v", path)
	err = os.WriteFile(filepath.Join(dataDir, "state.json"), file, 0o644)
	require.NoErrorf(t, err, "writing %v", path)
}
