package cachev2_test

import (
	"encoding/json"
	"sort"
	"testing"

	cache "github.com/fiskerinc/cloud-services/pkg/cachev2"
	"github.com/fiskerinc/cloud-services/pkg/common"
	"github.com/fiskerinc/cloud-services/pkg/mongo"
	"github.com/fiskerinc/cloud-services/pkg/redis"
	"github.com/fiskerinc/cloud-services/pkg/testhelper"
	"github.com/fiskerinc/cloud-services/pkg/utils/elptr"
	redigo "github.com/gomodule/redigo/redis"
	"github.com/stretchr/testify/assert"
)

func TestRetrieveVehicleConfig(t *testing.T) {
	setupRedisMock()
	id := "TESTVIN1234567"

	mockRedis = &mockRedisVehicleConfig{}
	config, err := cache.RetrieveVehicleConfig(mockRedis, mongo.NewMockClient(), id)
	if err != nil {
		t.Errorf(testhelper.TestErrorTemplate, "TestRetrieveVehicleConfig", nil, err)
	}
	data, err := json.Marshal(&config)
	if err != nil {
		t.Errorf(testhelper.TestErrorTemplate, "TestRetrieveVehicleConfig", nil, err)
	}
	assert.Equal(t, `{"canbus":{"enabled":false,"data_logger_enabled":false,"dtc_enabled":false},"log_level":"trace"}`, string(data))

	mockRedis = &mockRedisNoVehicleConfig{}
	config, err = cache.RetrieveVehicleConfig(mockRedis, mongo.NewMockClient(), id)
	if err != nil {
		t.Errorf(testhelper.TestErrorTemplate, "TestRetrieveVehicleConfig", nil, err)
	}
	data, err = json.Marshal(&config)
	if err != nil {
		t.Errorf(testhelper.TestErrorTemplate, "TestRetrieveVehicleConfig", nil, err)
	}
	assert.Equal(t, `{"canbus":{"enabled":false,"data_logger_enabled":false,"dtc_enabled":false},"log_level":"trace","log":{"matches":[{"channel":"cmd","level":"trace"}]}}`, string(data))
}

func TestRetrieveVehicleConfigDbgMask(t *testing.T) {
	setupRedisMock()
	id := "TESTVIN1234567"
	mockVehicle := mongo.Vehicle{VIN: id}
	mockRedis = &mockRedisNoVehicleConfig{}

	// validate that by default, retrieved debug value IS NOT passed to trxCfg
	trxCfg, err := cache.RetrieveVehicleConfig(mockRedis, mongo.NewMockClient(), id)
	existingValue := trxCfg.DebugMask
	assert.Nil(t, err)
	assert.NotNil(t, trxCfg)
	// assert that trxCfg value is unchanged
	assert.Equal(t, trxCfg.DebugMask, existingValue)

	// let us try to enable
	// the mock for redis is with no data so that code will fall through to the DB part
	// we ensure that what we get from DB has speific debug mask which should be
	// passed to Trex when the flag is true
	t.Setenv(cache.ENABLE_DBG_MASK_EV_NAME, cache.ENABLE_DBG_MASK_VAL_TRUE)
	cache.ENABLE_DEBUG_MASK = cache.DbgMaskEnabled()
	mmc := mongo.NewMockMongoClient()
	mockVehicle.DebugMask = "test"
	mmc.GetVehicles().AddVehicle(&mockVehicle)
	trxCfg, _ = cache.RetrieveVehicleConfig(mockRedis, mmc, id)
	// now validate that Trex config got the value as set in the mocked vehicle
	// (presumed as retrieved)
	assert.Equal(t, trxCfg.DebugMask, mockVehicle.DebugMask)

	// now set back the env variable so new values don't flow to trex
	t.Setenv(cache.ENABLE_DBG_MASK_EV_NAME, cache.ENABLE_DBG_MASK_VAL_FALSE)
	cache.ENABLE_DEBUG_MASK = cache.DbgMaskEnabled()
	oldMask := mockVehicle.DebugMask
	mockVehicle.DebugMask = "new-value"
	// skipping adding to the cache/DB as we still had the valid reference
	trxCfg, _ = cache.RetrieveVehicleConfig(mockRedis, mmc, id)
	// assert that trex does not have new value
	assert.NotEqual(t, trxCfg.DebugMask, oldMask)
}

func TestFiltersMap(t *testing.T) {
	filters := make(cache.FiltersMap)

	if len(filters) != 0 {
		t.Errorf(testhelper.TestErrorTemplate, "TestFiltersMap", 0, len(filters))
		return
	}

	emptyHex := common.NewBinaryHex([]byte{})
	bhex := common.BinaryHex("123")
	filters.AppendFilters(
		[]common.CANFilter{
			{CANID: "123", Interval: elptr.ElPtr(123)},
			{CANID: "456", Interval: elptr.ElPtr(456)},
			{CANID: "789", EdgeMask: &emptyHex},
			{CANID: "901", EdgeMask: &bhex},
			{CANID: "222", Interval: elptr.ElPtr(123), EdgeMask: &bhex},
			{CANID: "333", Interval: elptr.ElPtr(0)},
		},
	)
	if len(filters) != 5 {
		t.Errorf(testhelper.TestErrorTemplate, "TestFiltersMap", 5, len(filters))
		return
	}

	interval, ok := filters["123"]
	if !ok || *interval.Interval != 123 && interval.EdgeMask != nil {
		t.Errorf(testhelper.TestErrorTemplate, "TestFiltersMap", 123, "error")
		return
	}

	interval, ok = filters["456"]

	if !ok || *interval.Interval != 456 && interval.EdgeMask != nil {
		t.Errorf(testhelper.TestErrorTemplate, "TestFiltersMap", 456, "error")
		return
	}

	interval, ok = filters["789"]
	if ok || interval.EdgeMask != nil || interval.Interval != nil {
		t.Errorf(testhelper.TestErrorTemplate, "TestFiltersMap", emptyHex, "error")
		return
	}

	interval, ok = filters["901"]
	if !ok || interval.EdgeMask.String() != bhex.String() && interval.Interval != nil {
		t.Errorf(testhelper.TestErrorTemplate, "TestFiltersMap", bhex, "error")
		return
	}

	interval, ok = filters["222"]
	if !ok || interval.EdgeMask.String() != bhex.String() && interval.Interval != nil {
		t.Errorf(testhelper.TestErrorTemplate, "TestFiltersMap", bhex, "error")
		return
	}

	interval, ok = filters["333"]
	if !ok || interval.EdgeMask != nil && *interval.Interval != 0 {
		t.Errorf(testhelper.TestErrorTemplate, "TestFiltersMap", nil, "error")
		return
	}

	slice := filters.ToSlice()
	if len(slice) != 5 {
		t.Errorf(testhelper.TestErrorTemplate, "TestFiltersMap", 5, len(slice))
		return
	}

	sort.Slice(slice, func(i, j int) bool {
		return slice[i].CANID < slice[j].CANID
	})

	if slice[0].CANID != "123" {
		t.Errorf(testhelper.TestErrorTemplate, "TestFiltersMap", "123", slice[0].CANID)
		return
	}

	if slice[1].CANID != "222" {
		t.Errorf(testhelper.TestErrorTemplate, "TestFiltersMap", "222", slice[1].CANID)
		return
	}

	if slice[2].CANID != "333" {
		t.Errorf(testhelper.TestErrorTemplate, "TestFiltersMap", "333", slice[2].CANID)
		return
	}

	if slice[3].CANID != "456" {
		t.Errorf(testhelper.TestErrorTemplate, "TestFiltersMap", "456", slice[0].CANID)
		return
	}

	if slice[4].CANID != "901" {
		t.Errorf(testhelper.TestErrorTemplate, "TestFiltersMap", "901", slice[0].CANID)
		return
	}

}

type mockRedisVehicleConfig struct {
	redis.Connection
}

func (c *mockRedisVehicleConfig) Execute(command ...interface{}) (interface{}, error) {
	config := common.TRexConfigResponse{}
	data, _ := json.Marshal(config)
	return data, nil
}

type mockRedisNoVehicleConfig struct {
	redis.Connection
}

func (c *mockRedisNoVehicleConfig) Execute(command ...interface{}) (interface{}, error) {
	return nil, redigo.ErrNil
}

func (c *mockRedisNoVehicleConfig) ExecuteBatch(batch *redis.RedisBatchCommands) (interface{}, error) {
	return nil, nil
}
