package controllers

import (
	"github.com/fiskerinc/cloud-services/services/attendant/services"
	"encoding/base64"
	"encoding/json"
	"errors"
	"sync"
	"time"

	"github.com/fiskerinc/cloud-services/pkg/grpc/kafka_grpc"
	"github.com/fiskerinc/cloud-services/pkg/logger"
	"github.com/fiskerinc/cloud-services/pkg/can-go/pkg/descriptor"
)

type DTCEntry struct {
	CreatedAt time.Time `bson:"created_at"`
	VIN       string    `bson:"vin"`
	ECU       string    `json:"ecu" bson:"ecu"`
	DTC       uint64    `json:"dtc" bson:"dtc"`
	Status    uint8     `json:"status" bson:"status"`
	Timestamp time.Time `json:"timestamp" bson:"timestamp"`
	Speed     uint16    `json:"speed" bson:"speed"`
	Mileage   uint32    `json:"mileage" bson:"mileage"`
	Voltage   uint16    `json:"voltage" bson:"voltage"`

	SnapshotBase64 string `json:"snapshot,omitempty" bson:"snapshot,omitempty"`
}

func GRPCToDTCEntry(payload *kafka_grpc.GRPC_AttendantPayload) []byte {

	if payload.Data == nil {
		return nil
	}
	data := payload.Data.(*kafka_grpc.GRPC_AttendantPayload_DtcEntry)
	if data == nil {
		return nil
	}

	dtc := &DTCEntry{
		VIN:            data.DtcEntry.Vin,
		ECU:            data.DtcEntry.Ecu,
		CreatedAt:      milliToDate(data.DtcEntry.CreatedAt),
		DTC:            data.DtcEntry.Dtc,
		Status:         uint8(data.DtcEntry.Status),
		Timestamp:      milliToDate(data.DtcEntry.Timestamp),
		Speed:          uint16(data.DtcEntry.Speed),
		Mileage:        data.DtcEntry.Mileage,
		Voltage:        uint16(data.DtcEntry.Volt),
		SnapshotBase64: data.DtcEntry.SnapshotBase64,
	}
	bytes, _ := json.Marshal(dtc)
	return bytes
}
func milliToDate(timestamp int64) time.Time {
	seconds := timestamp / 1000
	nanoseconds := (timestamp % 1000) * int64(time.Millisecond)
	return time.Unix(seconds, nanoseconds)
}

const SNAPSHOT_LEN = 21

func (entry *DTCEntry) ParseSnapshot() error {

	data, errc := base64.StdEncoding.DecodeString(entry.SnapshotBase64)
	if errc != nil {
		return errc
	}
	payloadLen := len(data) - 2

	if payloadLen < SNAPSHOT_LEN {
		logger.Debug().Msgf("DTC snapshot payload is too small. Required length=%d, actual=%d, ECU=%s\n", SNAPSHOT_LEN, len(data), entry.ECU)
		return errors.New("Snapshot too small")
	}

	// Our "Basic Diagnostic" spec defines 4 mandatory DIDs that must be
	// stored inside DTCStapshotRecord:
	// 1. EF F6 - Timestamp (6 bytes).
	// 2. EF F7 - Vehicle Speed (2 bytes).
	// 3. EF F8 - Milage (idk, our spec doesn't specify exact number.
	//    Empirically it's 4 bytes (same as "ICC_0x531::TotMilg_ODO").
	// 4. EF F9 - Battery Voltage (2 bytes).
	///entry.Speed = int16(speed.ToPhysical(float64(uint16(decodedBytes[0])<<8 | uint16(decodedBytes[1]))))
	//entry.Voltage = int16(voltage.ToPhysical(float64(uint16(decodedBytes[0])<<8 | uint16(decodedBytes[1]))))

	for idx := 2; idx < len(data)-1; {
		if data[idx] != 0xEF {
			idx++
			continue
		}

		if data[idx+1] == 0xF6 && idx+7 < len(data) {
			idx += 2
			entry.Timestamp, idx = consumeTimestamp(data, idx)
		} else if data[idx+1] == 0xF7 && idx+3 < len(data) {
			idx += 2
			entry.Speed, idx = consumeSpeed(data, idx)
		} else if data[idx+1] == 0xF8 && idx+5 < len(data) {
			idx += 2
			entry.Mileage, idx = consumeMileage(data, idx)
		} else if data[idx+1] == 0xF9 && idx+3 < len(data) {
			idx += 2
			entry.Voltage, idx = consumeVoltage(data, idx)
		} else {
			idx++
		}
	}
	return nil
}

var onceTimestamp sync.Once
var (
	day   *descriptor.Signal
	hr    *descriptor.Signal
	mins  *descriptor.Signal
	yr    *descriptor.Signal
	month *descriptor.Signal
	sec   *descriptor.Signal
)

func consumeTimestamp(data []byte, idx int) (res time.Time, idx_ret int) {

	onceTimestamp.Do(func() {
		day, _ = services.GetDBC().Signal(0x62F, "TBOX_CrtTi_Day")
		hr, _ = services.GetDBC().Signal(0x62F, "TBOX_CrtTi_Hr")
		mins, _ = services.GetDBC().Signal(0x62F, "TBOX_CrtTi_Mins")
		yr, _ = services.GetDBC().Signal(0x62F, "TBOX_CrtTi_Yr")
		month, _ = services.GetDBC().Signal(0x62F, "TBOX_CrtTi_Mth")
		sec, _ = services.GetDBC().Signal(0x62F, "TBOX_CrtTi_Sec")

	})

	res = time.Date(int(yr.ToPhysical(float64(data[idx]))),
		time.Month(month.ToPhysical(float64(data[idx+1]))),
		int(day.ToPhysical(float64(data[idx+2]))),
		int(hr.ToPhysical(float64(data[idx+3]))),
		int(mins.ToPhysical(float64(data[idx+4]))),
		int(sec.ToPhysical(float64(data[idx+5]))), 0, time.UTC)
	idx_ret = idx + 6
	return res, idx_ret
}

var onceSpeed sync.Once
var speed *descriptor.Signal

func consumeSpeed(data []byte, idx int) (res uint16, idx_ret int) {

	onceSpeed.Do(func() {
		speed, _ = services.GetDBC().Signal(0x318, "ESP_VehSpd")
	})
	res = uint16(speed.ToPhysical(float64(uint16(data[idx])<<8 | uint16(data[idx+1]))))
	idx_ret = idx + 2
	return res, idx_ret
}

var onceMileage sync.Once
var mileage *descriptor.Signal

func consumeMileage(data []byte, idx int) (res uint32, idx_ret int) {
	onceMileage.Do(func() {
		mileage, _ = services.GetDBC().Signal(0x531, "ICC_TotMilg_ODO")
	})
	res = uint32(mileage.ToPhysical(
		float64(
			uint64(data[idx])<<24 |
				uint64(data[idx+1])<<16 |
				uint64(data[idx+2])<<8 | uint64(data[idx+3]))))
	idx_ret = idx + 4
	return res, idx_ret
}

var onceVoltage sync.Once
var voltage *descriptor.Signal

func consumeVoltage(data []byte, idx int) (res uint16, idx_ret int) {
	onceVoltage.Do(func() {
		voltage, _ = services.GetDBC().Signal(0x507, "VCU_BattVolt")
	})

	res = uint16(voltage.ToPhysical(float64(uint16(data[idx])<<8 | uint16(data[idx+1]))))
	idx_ret = idx + 2
	return res, idx_ret

}
