package preimage

import (
	"bytes"
	"crypto/rand"
	"fmt"
	"io"
	"sync"
	"testing"

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

type readWritePair struct {
	io.Reader
	io.Writer
}

func bidirectionalPipe() (a, b io.ReadWriter) {
	ar, bw := io.Pipe()
	br, aw := io.Pipe()
	return readWritePair{Reader: ar, Writer: aw}, readWritePair{Reader: br, Writer: bw}
}

func TestOracle(t *testing.T) {
	testPreimage := func(preimages ...[]byte) {
		a, b := bidirectionalPipe()
		cl := NewOracleClient(a)
		srv := NewOracleServer(b)

		preimageByHash := make(map[common.Hash][]byte)
		for _, p := range preimages {
			k := Keccak256Key(crypto.Keccak256Hash(p))
			preimageByHash[k.PreimageKey()] = p
		}
		for _, p := range preimages {
			k := Keccak256Key(crypto.Keccak256Hash(p))

			var wg sync.WaitGroup
			wg.Add(2)

			go func(k Key, p []byte) {
				result := cl.Get(k)
				wg.Done()
				expected := preimageByHash[k.PreimageKey()]
				require.True(t, bytes.Equal(expected, result), "need correct preimage %x, got %x", expected, result)
			}(k, p)

			go func() {
				err := srv.NextPreimageRequest(func(key common.Hash) ([]byte, error) {
					dat, ok := preimageByHash[key]
					if !ok {
						return nil, fmt.Errorf("cannot find %s", key)
					}
					return dat, nil
				})
				wg.Done()
				require.NoError(t, err)
			}()
			wg.Wait()
		}
	}
	t.Run("empty preimage", func(t *testing.T) {
		testPreimage([]byte{})
	})
	t.Run("nil preimage", func(t *testing.T) {
		testPreimage(nil)
	})
	t.Run("zero", func(t *testing.T) {
		testPreimage([]byte{0})
	})
	t.Run("multiple", func(t *testing.T) {
		testPreimage([]byte("tx from alice"), []byte{0x13, 0x37}, []byte("tx from bob"))
	})
	t.Run("zeroes", func(t *testing.T) {
		testPreimage(make([]byte, 1000))
	})
	t.Run("random", func(t *testing.T) {
		dat := make([]byte, 1000)
		_, _ = rand.Read(dat[:])
		testPreimage(dat)
	})
}
