package tester

import (
	"fmt"
	"regexp"
	"testing"

	"github.com/fiskerinc/cloud-services/pkg/common"
	"github.com/fiskerinc/cloud-services/pkg/testhelper"
)

type ExpiringCacheResult struct {
	Value        string
	Expires      int
	RegexCompare *regexp.Regexp
}

type RedisTestCase struct {
	Device            common.Device
	DeviceKey         string
	PayloadData       string
	ExpectedError     string
	ExpectedMessages  map[string]string
	ExpectedCaches    map[string]ExpiringCacheResult
	MockRedisError    error
	MockRedisGet      interface{}
	MockRedisGetCache string
	MockRedisRetrieve string
	MockRedisGetSet   string
	MockRedisGetMulti []interface{}
	Setup             func()
}

func (tc *RedisTestCase) SetupRedis(mockRedis *MockRedis) {
	if mockRedis != nil {
		mockRedis.Error = tc.MockRedisError
		mockRedis.GetCacheResults = tc.MockRedisGetCache
		mockRedis.GetResults = tc.MockRedisGet
		mockRedis.GetSetResults = tc.MockRedisGetSet
		mockRedis.RetrieveResult = tc.MockRedisRetrieve
		mockRedis.GetMultiResults = tc.MockRedisGetMulti
		if tc.Setup != nil {
			tc.Setup()
		}
	}
}

func (tc *RedisTestCase) CheckHandlerError(t *testing.T, name string, err error) {
	if err != nil && err.Error() != tc.ExpectedError {
		t.Errorf(testhelper.TestErrorTemplate, name, tc.ExpectedError, err.Error())
	} else if err == nil && tc.ExpectedError != "" {
		t.Errorf(testhelper.TestErrorTemplate, name, tc.ExpectedError, err)
	}
}

func (tc *RedisTestCase) Validate(t *testing.T, name string, mock *MockRedis) {
	tc.checkRedisMessages(t, name, mock)
	tc.checkRedisCache(t, name, mock)
}

func (tc *RedisTestCase) checkRedisMessages(t *testing.T, name string, mock *MockRedis) {
	for key, msg := range tc.ExpectedMessages {
		if compare, ok := mock.HasMessage(key, msg); !ok {
			t.Errorf(testhelper.TestErrorTemplate, fmt.Sprintf("%s CheckRedisMessages %s", name, key), msg, compare)
		}
	}

	for key := range mock.PublishedMessages {
		if _, ok := tc.ExpectedMessages[key]; !ok {
			t.Errorf(testhelper.TestErrorTemplate, fmt.Sprintf("%s unexpected message %s", name, key), nil, fmt.Sprintf("%s = %s", key, mock.PublishedMessages[key]))
		}
	}
}

func (tc *RedisTestCase) checkRedisCache(t *testing.T, name string, mock *MockRedis) {
	for key, expected := range tc.ExpectedCaches {
		tc.checkCacheValues(t, name, mock, key, expected)
	}

	for key := range mock.SetValues {
		if _, ok := tc.ExpectedCaches[key]; !ok {
			t.Errorf(testhelper.TestErrorTemplate, fmt.Sprintf("%s unexpected cache %s", name, key), nil, fmt.Sprintf("%s = %v", key, mock.SetValues[key]))
		}
	}
}

func (tc *RedisTestCase) getCacheValue(mock *MockRedis, cache ExpiringCache) (string, error) {
	return cache.StringValue()
}

func (tc *RedisTestCase) checkCacheValues(t *testing.T, test string, mock *MockRedis, key string, expected ExpiringCacheResult) {
	name := fmt.Sprintf("%s checkCacheValues %s", test, key)
	if cached, ok := mock.FetchCache(key); ok {
		value, err := tc.getCacheValue(mock, cached)
		if err != nil {
			t.Error(err)
		}

		if expected.RegexCompare != nil {
			if !expected.RegexCompare.Match([]byte(value)) {
				t.Errorf(testhelper.TestErrorTemplate, name, expected.RegexCompare.String(), value)
			}
		} else if value != expected.Value {
			t.Errorf(testhelper.TestErrorTemplate, name, expected.Value, value)
		}

		if expected.Expires != cached.Expires {
			t.Errorf(testhelper.TestErrorTemplate, name, expected.Expires, cached.Expires)
		}
	} else {
		t.Errorf(testhelper.TestErrorTemplate, name, fmt.Sprintf("Has Cache %s", key), nil)
	}
}
