package runtime

import (
	"context"
	"math/rand"
	"sync"
	"testing"
	"time"

	internallogger "github.com/wailsapp/wails/v2/internal/logger"
	"github.com/wailsapp/wails/v2/internal/servicebus"
	"github.com/wailsapp/wails/v2/pkg/logger"

	is2 "github.com/matryer/is"
)

func TestStoreProvider_NewWithNilDefault(t *testing.T) {
	is := is2.New(t)

	defaultLogger := logger.NewDefaultLogger()
	testLogger := internallogger.New(defaultLogger)
	//testLogger.SetLogLevel(logger.TRACE)
	serviceBus := servicebus.New(testLogger)
	err := serviceBus.Start()
	is.NoErr(err)
	defer serviceBus.Stop()

	testRuntime := New(serviceBus)
	storeProvider := newStore(testRuntime)

	testStore := storeProvider.New("test", 0)

	// You should be able to write a new value into a
	// store initialised with nil
	err = testStore.Set(100)
	is.NoErr(err)

	// You shouldn't be able to write different types to the
	// store
	err = testStore.Set(false)
	is.True(err != nil)
}

func TestStoreProvider_NewWithScalarDefault(t *testing.T) {
	is := is2.New(t)

	defaultLogger := logger.NewDefaultLogger()
	testLogger := internallogger.New(defaultLogger)
	//testLogger.SetLogLevel(logger.TRACE)
	serviceBus := servicebus.New(testLogger)
	err := serviceBus.Start()
	is.NoErr(err)
	defer serviceBus.Stop()

	testRuntime := New(serviceBus)
	storeProvider := newStore(testRuntime)
	testStore := storeProvider.New("test", 100)
	value := testStore.Get()
	is.Equal(value, 100)
	testStore.resync()
	value = testStore.Get()
	is.Equal(value, 100)
}

func TestStoreProvider_NewWithStructDefault(t *testing.T) {
	is := is2.New(t)

	defaultLogger := logger.NewDefaultLogger()
	testLogger := internallogger.New(defaultLogger)
	//testLogger.SetLogLevel(logger.TRACE)
	serviceBus := servicebus.New(testLogger)
	err := serviceBus.Start()
	is.NoErr(err)
	defer serviceBus.Stop()

	testRuntime := New(serviceBus)
	storeProvider := newStore(testRuntime)

	type TestValue struct {
		Name string
	}
	testValue := &TestValue{
		Name: "hi",
	}

	testStore := storeProvider.New("test", testValue)

	err = testStore.Set(testValue)
	is.NoErr(err)
	testStore.resync()
	value := testStore.Get()
	is.Equal(value, testValue)
	is.Equal(value.(*TestValue).Name, "hi")

	testValue = &TestValue{
		Name: "there",
	}
	err = testStore.Set(testValue)
	is.NoErr(err)
	testStore.resync()
	value = testStore.Get()
	is.Equal(value, testValue)
	is.Equal(value.(*TestValue).Name, "there")

}

func TestStoreProvider_RapidReadWrite(t *testing.T) {
	is := is2.New(t)

	defaultLogger := logger.NewDefaultLogger()
	testLogger := internallogger.New(defaultLogger)
	//testLogger.SetLogLevel(logger.TRACE)
	serviceBus := servicebus.New(testLogger)
	err := serviceBus.Start()
	is.NoErr(err)
	defer serviceBus.Stop()

	testRuntime := New(serviceBus)
	storeProvider := newStore(testRuntime)

	testStore := storeProvider.New("test", 1)

	ctx, _ := context.WithTimeout(context.Background(), 3*time.Second)

	var wg sync.WaitGroup
	readers := 100
	writers := 100
	wg.Add(readers + writers)
	// Setup readers
	go func(testStore *Store, ctx context.Context) {
		for readerCount := 0; readerCount < readers; readerCount++ {
			go func(store *Store, ctx context.Context, id int) {
				for {
					select {
					case <-ctx.Done():
						wg.Done()
						return
					default:
						store.Get()
					}
				}
			}(testStore, ctx, readerCount)
		}
	}(testStore, ctx)

	// Setup writers
	go func(testStore *Store, ctx context.Context) {
		for writerCount := 0; writerCount < writers; writerCount++ {
			go func(store *Store, ctx context.Context, id int) {
				for {
					select {
					case <-ctx.Done():
						wg.Done()
						return
					default:
						err := store.Set(rand.Int())
						is.NoErr(err)
					}
				}
			}(testStore, ctx, writerCount)
		}
	}(testStore, ctx)

	wg.Wait()
}
