package dbc_test

import (
	"fmt"
	"reflect"
	"regexp"
	"testing"

	"github.com/fiskerinc/cloud-services/pkg/dbc"
	fm29_frs90 "github.com/fiskerinc/cloud-services/pkg/dbc/fm29_frs90"
	fm29_frsd0 "github.com/fiskerinc/cloud-services/pkg/dbc/fm29_frsd0"
	fm29_frsd21 "github.com/fiskerinc/cloud-services/pkg/dbc/fm29_frsd21"
	fm29_frsd390 "github.com/fiskerinc/cloud-services/pkg/dbc/fm29_frsd390"

	"github.com/fiskerinc/cloud-services/pkg/dbc/state"
	"github.com/fiskerinc/cloud-services/pkg/grpc/kafka_grpc"
	"github.com/fiskerinc/cloud-services/pkg/testhelper"
	"github.com/golang/protobuf/proto"
	"github.com/jinzhu/copier"
)

const vin = "11111111111111111"

var epoch = int64(1642455023642165)
var payloadFRS90 = kafka_grpc.GRPC_BatchPayload{
	Version: fm29_frs90.Hash,
	Data: &kafka_grpc.GRPC_CANData{
		Frames: []*kafka_grpc.GRPC_CANFrame{
			{
				Epoch: epoch,
				ID:    608,
				Value: []byte("AAEAAAAAAAA="),
			},
			{
				Epoch: epoch,
				ID:    757,
				Value: []byte("CgAAAAAUAAA="),
			},
			{
				Epoch: epoch,
				ID:    801,
				Value: []byte("AAAUKDxQ/wA="),
			},
			{
				Epoch: epoch,
				ID:    816,
				Value: []byte("AAAAfQAAAAA="),
			},
			{
				Epoch: epoch,
				ID:    821,
				Value: []byte("AAAAAAgAAP8="),
			},
			{
				Epoch: epoch,
				ID:    835,
				Value: []byte("AAAFbAAAMgA="),
			},
			{
				Epoch: epoch,
				ID:    873,
				Value: []byte("AAB4jKAAAAE="),
			},
			{
				Epoch: epoch,
				ID:    882,
				Value: []byte("AAAwADCKADA="),
			},
			{
				Epoch: epoch,
				ID:    883,
				Value: []byte("AAAAAACcAAA="),
			},
			{
				Epoch: epoch,
				ID:    902,
				Value: []byte("AQAAAAAAAAA="),
			},
			{
				Epoch: epoch,
				ID:    1284,
				Value: []byte("AAAAAAAwNAA="),
			},
			{
				Epoch: epoch,
				ID:    1298,
				Value: []byte("EAAAAAAAAAA="),
			},
			{
				Epoch: epoch,
				ID:    1304,
				Value: []byte("AgAAAAAAAAA="),
			},
			{
				Epoch: epoch,
				ID:    1329,
				Value: []byte("AAADlAQAAAA="),
			},
			{
				Epoch: epoch,
				ID:    1408,
				Value: []byte("AAAAABOIAAA="),
			},
			{
				Epoch: epoch,
				ID:    1410,
				Value: []byte("nAB4AAAAAAA="),
			},
			{
				Epoch: epoch,
				ID:    1317,
				Value: []byte("AABAg6NJ64c="),
			},
		},
	},
}
var payloadFRSD0 = kafka_grpc.GRPC_BatchPayload{
	Version: fm29_frsd0.Hash,
	Data: &kafka_grpc.GRPC_CANData{
		Frames: []*kafka_grpc.GRPC_CANFrame{
			{
				Epoch: epoch,
				ID:    608,
				Value: []byte("AAAA+gH0Aec="),
			},
			{
				Epoch: epoch,
				ID:    757,
				Value: []byte("CgAAAAAUAAA="),
			},
			{
				Epoch: epoch,
				ID:    801,
				Value: []byte("AAAUKDxQ/wA="),
			},
			{
				Epoch: epoch,
				ID:    816,
				Value: []byte("AAAAfQAAAAA="),
			},
			{
				Epoch: epoch,
				ID:    821,
				Value: []byte("AAAAAAgAAP8="),
			},
			{
				Epoch: epoch,
				ID:    835,
				Value: []byte("AAAFbAAAZAA="),
			},
			{
				Epoch: epoch,
				ID:    873,
				Value: []byte("AAB4jKAAAAE="),
			},
			{
				Epoch: epoch,
				ID:    882,
				Value: []byte("AAAwADCKADA="),
			},
			{
				Epoch: epoch,
				ID:    883,
				Value: []byte("AAAAAACcAAA="),
			},
			{
				Epoch: epoch,
				ID:    902,
				Value: []byte("AQAAAAAAAAA="),
			},
			{
				Epoch: epoch,
				ID:    1284,
				Value: []byte("AAAAAAAwNAA="),
			},
			{
				Epoch: epoch,
				ID:    1298,
				Value: []byte("EAAAAAAAAAA="),
			},
			{
				Epoch: epoch,
				ID:    1304,
				Value: []byte("AgAAAAAAAAA="),
			},
			{
				Epoch: epoch,
				ID:    1329,
				Value: []byte("AAADlAQAAAA="),
			},
			{
				Epoch: epoch,
				ID:    1408,
				Value: []byte("AMgBLBOIAZA="),
			},
			{
				Epoch: epoch,
				ID:    1410,
				Value: []byte("nAB4AAAAAAA="),
			},
			{
				Epoch: epoch,
				ID:    1317,
				Value: []byte("ZAVAgAAAAAA="),
			},
			{
				Epoch: epoch,
				ID:    1318,
				Value: []byte("A414OAeAC0A="),
			},
		},
	},
}

var expectedValuesFRS90 = map[string]interface{}{
	state.BCM_AP_FL_LeFrntWinPosnInfo: interface{}(10),
	state.BCM_AP_FL_LeReWinPosnInfo:   interface{}(30),
	state.BCM_AP_FL_RiFrntWinPosnInfo: interface{}(20),
	state.BCM_AP_FL_RiReWinPosnInfo:   interface{}(40),
	state.BCM_AP_RW_WinPosnInfo:       interface{}(80),
	state.BCM_AP_TL_LeReWinPosnInfo:   interface{}(60),
	state.BCM_AP_TL_RiReWinPosnInfo:   interface{}(70),
	state.BCM_CenLockSwtSts:           interface{}(3),
	state.BCM_DrFrntDoorSts:           interface{}(false),
	state.BCM_FrntDrDoorLockSts:       interface{}(true),
	state.BCM_FrntHoodLidSts:          interface{}(true),
	state.BCM_HeatedSteerWhlSt:        interface{}(true),
	state.BCM_LeReDoorSts:             interface{}(true),
	state.BCM_PasFrntDoorSts:          interface{}(false),
	state.BCM_ReDefrstHeatgCmd:        interface{}(true),
	state.BCM_RiReDoorSts:             interface{}(true),
	state.BCM_SunroofPosnInfo:         interface{}(50),
	state.BMS_Bat_SOH:                 interface{}(20),
	state.BMS_Bat_SoC_usable:          interface{}(10),
	state.BMS_BattAvrgT:               interface{}(90),
	state.BMS_PwrBattRmngCpSOC:        interface{}(50),
	state.DSMC_DrvrSeatHeatgSts:       interface{}(2),
	state.ECC_InsdT:                   interface{}(30),
	state.ECC_OutdT:                   interface{}(30),
	state.ECC_RemTSetSts:              interface{}(120),
	state.ICC_TotMilg_ODO:             interface{}(float64(2345)),
	state.PSM_PassSeatHeatgSts:        interface{}(4),
	state.TBOX_GPSHei:                 interface{}(float64(16)),
	state.TBOX_GPSLati:                interface{}(float64(35.831)),
	state.TBOX_GPSLongi:               interface{}(float64(-120.398)),
	state.VCU_DCChrgRmngTi:            interface{}(5000),
	state.VCU_DrvgMilg:                interface{}(1234),
	state.VCU_VehChrgDchgMod:          interface{}("DC_charging"),
	state.UPDATED_AT:                  interface{}([]byte{34, 50, 48, 50, 50, 45, 48, 49, 45, 49, 55, 84, 50, 49, 58, 51, 48, 58, 50, 51, 46, 54, 52, 50, 49, 54, 53, 90, 34}),
}
var expectedValuesFRSD0 = map[string]interface{}{
	state.BCM_AP_FL_LeFrntWinPosnInfo: interface{}(10),
	state.BCM_AP_FL_LeReWinPosnInfo:   interface{}(30),
	state.BCM_AP_FL_RiFrntWinPosnInfo: interface{}(20),
	state.BCM_AP_FL_RiReWinPosnInfo:   interface{}(40),
	state.BCM_AP_RW_WinPosnInfo:       interface{}(80),
	state.BCM_AP_TL_LeReWinPosnInfo:   interface{}(60),
	state.BCM_AP_TL_RiReWinPosnInfo:   interface{}(70),
	state.BCM_CenLockSwtSts:           interface{}(3),
	state.BCM_DrFrntDoorSts:           interface{}(false),
	state.BCM_FrntDrDoorLockSts:       interface{}(true),
	state.BCM_FrntHoodLidSts:          interface{}(true),
	state.BCM_HeatedSteerWhlSt:        interface{}(true),
	state.BCM_LeReDoorSts:             interface{}(true),
	state.BCM_PasFrntDoorSts:          interface{}(false),
	state.BCM_ReDefrstHeatgCmd:        interface{}(true),
	state.BCM_RiReDoorSts:             interface{}(true),
	state.BCM_SunroofPosnInfo:         interface{}(50),
	state.BMS_Bat_SOH:                 interface{}(20),
	state.BMS_Bat_SoC_usable:          interface{}(10),
	state.BMS_BattAvrgT:               interface{}(90),
	state.BMS_PwrBattRmngCpSOC:        interface{}(50),
	state.DSMC_DrvrSeatHeatgSts:       interface{}(2),
	state.ECC_InsdT:                   interface{}(30),
	state.ECC_OutdT:                   interface{}(30),
	state.ECC_RemTSetSts:              interface{}(120),
	state.ICC_TotMilg_ODO:             interface{}(float64(2345)),
	state.PSM_PassSeatHeatgSts:        interface{}(4),
	state.TBOX_GPSHei:                 interface{}(float64(16)),
	state.TBOX_GPSLati:                interface{}(float64(35.832)),
	state.TBOX_GPSLongi:               interface{}(float64(-120.397)),
	state.VCU_DCChrgRmngTi:            interface{}(5000),
	state.VCU_DrvgMilg:                interface{}(1234),
	state.VCU_VehChrgDchgMod:          interface{}("DC_charging"),
	state.UPDATED_AT:                  interface{}([]byte{34, 50, 48, 50, 50, 45, 48, 49, 45, 49, 55, 84, 50, 49, 58, 51, 48, 58, 50, 51, 46, 54, 52, 50, 49, 54, 53, 90, 34}),
}

var expectedValuesFRSD21 = map[string]interface{}{
	state.BCM_AP_FL_LeFrntWinPosnInfo: interface{}(10),
	state.BCM_AP_FL_LeReWinPosnInfo:   interface{}(30),
	state.BCM_AP_FL_RiFrntWinPosnInfo: interface{}(20),
	state.BCM_AP_FL_RiReWinPosnInfo:   interface{}(40),
	state.BCM_AP_RW_WinPosnInfo:       interface{}(80),
	state.BCM_AP_TL_LeReWinPosnInfo:   interface{}(60),
	state.BCM_AP_TL_RiReWinPosnInfo:   interface{}(70),
	state.BCM_CenLockSwtSts:           interface{}(3),
	state.BCM_DrFrntDoorSts:           interface{}(false),
	state.BCM_FrntDrDoorLockSts:       interface{}(true),
	state.BCM_FrntHoodLidSts:          interface{}(true),
	state.BCM_HeatedSteerWhlSt:        interface{}(true),
	state.BCM_LeReDoorSts:             interface{}(true),
	state.BCM_PasFrntDoorSts:          interface{}(false),
	state.BCM_ReDefrstHeatgCmd:        interface{}(true),
	state.BCM_RiReDoorSts:             interface{}(true),
	state.BCM_SunroofPosnInfo:         interface{}(50),
	state.BMS_Bat_SOH:                 interface{}(20),
	state.BMS_Bat_SoC_usable:          interface{}(10),
	state.BMS_BattAvrgT:               interface{}(90),
	state.BMS_PwrBattRmngCpSOC:        interface{}(50),
	state.DSMC_DrvrSeatHeatgSts:       interface{}(2),
	state.ECC_InsdT:                   interface{}(30),
	state.ECC_OutdT:                   interface{}(30),
	state.ECC_RemTSetSts:              interface{}(120),
	state.ICC_TotMilg_ODO:             interface{}(2345),
	state.PSM_PassSeatHeatgSts:        interface{}(4),
	state.TBOX_GPSHei:                 interface{}(float64(2802.4)),
	state.TBOX_GPSLati:                interface{}(float64(-54.168)),
	state.TBOX_GPSLongi:               interface{}(float64(-300.397)),
	state.VCU_DCChrgRmngTi:            interface{}(5000),
	state.VCU_DrvgMilg:                interface{}(1234),
	state.VCU_VehChrgDchgMod:          interface{}("DC_charging"),
	state.UPDATED_AT:                  interface{}([]byte{34, 50, 48, 50, 50, 45, 48, 49, 45, 49, 55, 84, 50, 49, 58, 51, 48, 58, 50, 51, 46, 54, 52, 50, 49, 54, 53, 90, 34}),
}

func getExpectedValuesFRS390() map[string]interface{} {
	expectedValuesFRS390 := map[string]interface{}{}
	copier.Copy(&expectedValuesFRS390, &expectedValuesFRSD21)
	// DBC 390 does not have a definition for VCU_DCChrgRmngTi
	delete(expectedValuesFRS390, state.VCU_DCChrgRmngTi)
	expectedValuesFRS390[state.TBOX_GPSLati] = interface{}(float64(35.832))
	expectedValuesFRS390[state.TBOX_GPSLongi] = interface{}(float64(-120.397))

	return expectedValuesFRS390
}

func copyPayload(source *kafka_grpc.GRPC_BatchPayload, hash string) *kafka_grpc.GRPC_BatchPayload {
	payload := kafka_grpc.GRPC_BatchPayload{}
	proto.Merge(&payload, source)
	payload.Version = hash

	return &payload
}

func ignoreSignalUpdateTimestamp(input map[string]interface{}) map[string]interface{} {
	m := make(map[string]interface{})
	pattern, _ := regexp.Compile(`:updated$`)
	for key, val := range input {
		matched := pattern.MatchString(key)
		if matched {
			continue
		}
		m[key] = val
	}
	return m
}

func TestFM29_FRS90_ParseState(t *testing.T) {
	payloadFRSD21 := copyPayload(&payloadFRSD0, fm29_frsd21.Hash)
	payloadFRSD390 := copyPayload(&payloadFRSD0, fm29_frsd390.Hash)
	expectedValuesFRS390 := getExpectedValuesFRS390()

	collection := dbc.NewDBCCollection()
	type testCase struct {
		name                 string
		payload              *kafka_grpc.GRPC_BatchPayload
		expected             map[string]interface{}
		expectedCommandCount int
	}
	tests := []testCase{
		{
			name:                 "FRS90",
			payload:              &payloadFRS90,
			expected:             expectedValuesFRS90,
			expectedCommandCount: 34,
		},
		{
			name:                 "FRSD0",
			payload:              &payloadFRSD0,
			expected:             expectedValuesFRSD0,
			expectedCommandCount: 34,
		},
		{
			name:                 "FRSD21",
			payload:              payloadFRSD21,
			expected:             expectedValuesFRSD21,
			expectedCommandCount: 34,
		},
		{
			name:                 "FRSD390",
			payload:              payloadFRSD390,
			expected:             expectedValuesFRS390,
			expectedCommandCount: 33,
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			keyvalues, err := collection.ParseState(vin, test.payload, nil)
			if err != nil {
				t.Errorf(testhelper.TestErrorTemplate, fmt.Sprintf("ParseState %s", test.name), nil, err)
			}
			expectedCommandCount := 2*test.expectedCommandCount - 1
			if len(keyvalues) != expectedCommandCount {
				t.Errorf(testhelper.TestErrorTemplate, fmt.Sprintf("key value len %s", test.name), expectedCommandCount, len(keyvalues))
			}
			mKeyVal := ignoreSignalUpdateTimestamp(keyvalues)
			if !reflect.DeepEqual(mKeyVal, test.expected) {
				t.Errorf(testhelper.TestErrorTemplate, fmt.Sprintf("key value compare %s", test.name), test.expected, keyvalues)
			}
		})
	}
}

func TestBadMsgID(t *testing.T) {
	collection := dbc.NewDBCCollection()
	payload := kafka_grpc.GRPC_BatchPayload{
		Version: fm29_frsd0.Hash,
		Data: &kafka_grpc.GRPC_CANData{
			Frames: []*kafka_grpc.GRPC_CANFrame{
				{
					Epoch: epoch,
					ID:    576,
					Value: []byte("AAAAA414OAeAC0A="),
				},
			},
		},
	}

	// This test should not set anything in Redis cache
	keyvalues, _ := collection.ParseState(vin, &payload, nil)
	if len(keyvalues) != 0 {
		t.Errorf(testhelper.TestErrorTemplate, "bad message id", 0, len(keyvalues))
	}
}

func BenchmarkNewFM29_FRS90_DBC(b *testing.B) {
	for n := 0; n < b.N; n++ {
		dbc.NewFM29_FRS90_DBC()
	}
}

func BenchmarkParseState(b *testing.B) {
	collection := dbc.NewDBCCollection()

	for n := 0; n < b.N; n++ {
		collection.ParseState(vin, &payloadFRS90, nil)
	}
}
