package queries

import (
	"fmt"
	"sort"
	"strconv"
	"strings"
	"time"

	"github.com/fiskerinc/cloud-services/pkg/common"
	"github.com/fiskerinc/cloud-services/pkg/logger"
	"github.com/fiskerinc/cloud-services/pkg/validator"
	"github.com/fiskerinc/cloud-services/pkg/vindecoder"

	"github.com/go-pg/pg/v10"
	"github.com/go-pg/pg/v10/orm"
	"github.com/pkg/errors"
	errors0 "errors"
)

type CarsInterface interface {
	Count(filter *common.Car) (int, error)
	Delete(car *common.Car) (orm.Result, error)
	Insert(car *common.Car) (orm.Result, error)
	Select(filter *common.Car, paging *PageQueryOptions) ([]common.Car, error)
	SelectByVIN(vin string) (*common.Car, error)
	SelectOrInsert(car *common.Car) (bool, error)
	Update(car *common.Car) (orm.Result, error)
	UpdateICCID(car *common.Car) (orm.Result, error)
	UpdateSoldStatus(car *common.Car) (orm.Result, error)
	Load(car *common.Car) error
	AddDriver(car *common.Car, driver *common.Driver, role string) (*common.CarToDriver, error)
	RemoveDriver(vin string, driverID string) (orm.Result, error)
	SelectCarToDriver(filter *common.CarToDriver) ([]common.CarToDriver, error)
	GetDrivers(vin string) ([]common.CarToDriver, error)
	GetTRexSetting(vin string) (common.TRexSetting, error)
	GetModels() ([]string, error)
	GetYears() ([]int, error)
	SetSetting(setting *common.CarSetting) (orm.Result, error)
	GetSettings(driver *common.CarToDriver) ([]common.CarSetting, error)
	GetVehicleSpecificSettings(driver *common.CarToDriver) ([]common.CarSetting, error)
	DeleteSetting(setting *common.CarSetting) (orm.Result, error)
	Search(*common.CarSearch, *PageQueryOptions) ([]common.Car, error)
	SearchCount(*common.CarSearch) (int, error)
	UpdateCarECU(*common.CarECU) error
	UpdateCarECUs([]common.CarECU) error
	InsertCarECUs([]common.CarECU) error
	GetCarECUs(filter common.CarECUFilter, paging *PageQueryOptions) ([]common.CarECU, error)
	GetCarECUsCount(filter common.CarECUFilter) (int, error)
	UpdateCarFlashpackVersion(vin string, flashpack string) (orm.Result, error)
	GetFlashpackVersions(carModel string, carTrim string, carYear int, options *PageQueryOptions) ([]common.CarFlashpackVersionResponse, error)
	GetFlashpackVersionsCount(carModel string, carTrim string, carYear int) (int, error)
	GetNextFlashpackVersion(carModel string, carTrim string, flashpack string) (*common.CarFlashpackVersionResponse, error)
	DeleteFlashpackVersion(carModel string, carTrim string, carYear int, flashpack string) error
	GetCarFlashpackVersionMappingsByModelTrim(carModel string, carTrim string, options *PageQueryOptions) ([]common.CarFlashpackVersion, error)
	GetCarFlashpackVersionMappingsByModelTrimYearFlashpack(carModel string, carTrim string, carYear int, flashpack string, options *PageQueryOptions) ([]common.CarFlashpackVersion, error)
	GetCarFlashpackVersionMappingsByModelTrimYearFlashpackCount(carModel string, carTrim string, carYear int, flashpack string) (int, error)
	GetCarECUNamesByModelTrim(carModel string, carTrim string) ([]string, error)
	AddCarFlashpackVersionMappings(carFlashpackVersions []common.CarFlashpackVersion) error
	GetCarsForDriver(driverID string) ([]common.CarToDriver, error)
	CarsByManifest(manifest common.UpdateManifest, paging *PageQueryOptions) ([]common.Car, error)
	CountCarsByManifest(manifest common.UpdateManifest) (int, error)
	UpdateBLEKey(vin string, driverid string, blekey string) (externalUser common.DriverExternal, err error)
	ECUUpdatedAt(ecu common.CarECU) (orm.Result, error)
	// Weakly associated American Lease data
	GetSoftwareAndPKCVersions(vins []string) (results []common.CarPKCOSVersion, err error)
	GetSoftwareVersion(vin string)(result common.CarPKCOSVersion, err error) // Function is incomplete for its final true meaning

	GetWhiteListCars()(vins []string, err error)
	WhitelistCars(vins []string, source string)(err error)
	BlacklistCars(vins []string)(err error)
	GetICCIDs(vins []string)(iccids []string, err error)
}

// Cars query methods
type Cars struct {
	QueryBase
}

func (c *Cars) ECUUpdatedAt(ecu common.CarECU) (orm.Result, error) {
	now := time.Now()
	ecu.UpdatedAt = &now
	return c.GetDBConn().Model(&ecu).Column("updated_at").Where("vin = ?vin AND ecu = ?ecu").Update()
}

func (c *Cars) SearchCount(filter *common.CarSearch) (int, error) {
	query := c.GetDBConn().Model((*common.Car)(nil))

	c.searchFilter(query, filter)

	count, err := query.Count()

	return count, errors.WithStack(err)
}

func (c *Cars) Search(filter *common.CarSearch, paging *PageQueryOptions) ([]common.Car, error) {
	cars := []common.Car{}
	query := c.GetDBConn().Model(&cars)

	c.searchFilter(query, filter)
	c.pageQuery(query, paging)

	err := query.Select()

	return cars, errors.WithStack(err)
}

func (c *Cars) CarsByManifest(manifest common.UpdateManifest, paging *PageQueryOptions) ([]common.Car, error) {
	var cars []common.Car
	query := c.GetDBConn().Model(&cars)
	query = c.manifestFilter(query, manifest)
	err := c.pageQuery(query, paging).Select()
	if err != nil {
		return nil, errors.WithStack(err)
	}

	return cars, nil
}

func (c *Cars) CountCarsByManifest(manifest common.UpdateManifest) (int, error) {
	var cars []common.Car
	query := c.GetDBConn().Model(&cars)
	query = c.manifestFilter(query, manifest)
	total, err := query.Count()
	if err != nil {
		return 0, errors.WithStack(err)
	}

	return total, nil
}

func (c *Cars) manifestFilter(query *orm.Query, manifest common.UpdateManifest) *orm.Query {
	if manifest.PowerTrain != "" {
		query.Where("powertrain = ?", manifest.PowerTrain)
	}
	if manifest.Country != "" {
		query.Where("country = ?", manifest.Country)
	}
	if manifest.Restraint != "" {
		query.Where("restraint = ?", manifest.Restraint)
	}
	if manifest.BodyType != "" {
		query.Where("body_type = ?", manifest.BodyType)
	}
	if manifest.Year != 0 {
		query.Where("year = ?", manifest.Year)
	}
	if manifest.Model != "" {
		query.Where("model = ?", manifest.Model)
	}
	if manifest.Trim != "" {
		query.Where("trim = ?", manifest.Trim)
	}

	return query
}

func (c *Cars) searchFilter(query *orm.Query, filter *common.CarSearch) {
	if filter.Search != "" {
		query.Where("document @@ plainto_tsquery(?) OR vin ILIKE ?", filter.Search, fmt.Sprintf("%%%s%%", filter.Search))
	}

	if filter.VINs != "" {
		c.queryVINsWhereIn(query, filter.VINs)
	}

	AddOnlineFilter(query, filter)

	c.selectFilter(query, &filter.Car)

	if filter.NoEU {
		query.Where("region != ?", common.EU)
	}
}

// AddOnlineFilter filters only the online cars, despite the VINsOnline|HMIsOnline being empty or not.
// Probably it needs to be polished so that HMIsOnline and VINsOnline would work separately.
func AddOnlineFilter(query *orm.Query, filter *common.CarSearch) {
	if filter.Online == nil {
		return
	}

	var isOnline, nilOnline bool
	if filter.Online.Online != nil {
		isOnline = *filter.Online.Online
	} else {
		nilOnline = true
	}

	if filter.Online.HMI != nil {
		isOnline = *filter.Online.HMI || isOnline
		nilOnline = false
	}

	if nilOnline {
		return
	}

	onlineOperator := "IN"
	if !isOnline {
		onlineOperator = "NOT IN"
	}

	if len(filter.Online.VINsOnline) == 0 {
		filter.Online.VINsOnline = []string{""}
	}

	query.Where("vin "+onlineOperator+" (?)", pg.In(filter.Online.VINsOnline))
}

func (c *Cars) selectFilter(query *orm.Query, filter *common.Car) {
	if filter.VIN != "" && len(filter.VIN) == 17 {
		query.Where("vin = ?", filter.VIN)
	}

	if filter.VIN != "" && len(filter.VIN) == 7 {
		query.Where("vin LIKE ?", "%"+filter.VIN)
	}

	if filter.Model != "" {
		query.Where("model = ?", filter.Model)
	}

	if filter.Year > 0 {
		query.Where("year = ?", filter.Year)
	}
}

func (c *Cars) queryVINsWhereIn(query *orm.Query, vinCSV string) {
	vins := strings.Split(vinCSV, ",")

	for i, vin := range vins {
		valid := vindecoder.ValidateVINSimple(vin)
		if valid {
			valid = vindecoder.VerifyVinCheckDigit(vin)
		}

		if i == 0 {
			if valid {
				query.Where("vin = ?", vin)
			} else {
				query.Where("vin LIKE ?", "%"+vin)
			}
		} else {
			if valid {
				query.WhereOr("vin = ?", vin)
			} else {
				query.WhereOr("vin LIKE ?", "%"+vin)
			}
		}
	}
}

// SelectByVIN returns a car by VIN
func (c *Cars) SelectByVIN(vin string) (*common.Car, error) {
	car := common.Car{}

	query := c.GetDBConn().
		Model(&car).
		Column("car.*", "sv.os_version").
		Join("LEFT JOIN sums_versions AS sv ON car.sums_version = sv.version").
		Where("car.vin = ?", vin)

	err := query.Select()
	if err != nil {
		return nil, errors.WithStack(err)
	}

	return &car, nil
}

// Select returns list of cars
func (c *Cars) Select(filter *common.Car, paging *PageQueryOptions) ([]common.Car, error) {
	cars := []common.Car{}
	query := c.GetDBConn().Model(&cars)

	c.selectFilter(query, filter)
	c.pageQuery(query, paging)

	err := query.Select()

	return cars, errors.WithStack(err)
}

func (c *Cars) SelectOrInsert(car *common.Car) (bool, error) {
	car.VIN = strings.ToUpper(car.VIN)
	q := c.GetDBConn().Model(car)

	if car.VIN != "" {
		q.Where("vin = ?vin")
	} else {
		return false, errors.New("no VIN")
	}

	inserted, err := q.SelectOrInsert()

	return inserted, errors.WithStack(err)
}

func (c *Cars) Delete(car *common.Car) (orm.Result, error) {
	var err error

	if car.VIN == "" {
		return nil, errors.WithStack(&validator.FieldError{
			ErrorMsg: "VIN required",
		})
	}

	tx, err := c.GetDBConn().Begin()
	if err != nil {
		return nil, errors.WithStack(err)
	}
	defer func() {
		if err != nil {
			tx.Rollback()
		}
		tx.Close()
	}()

	_, err = tx.Exec("DELETE FROM subscriptions WHERE car_to_driver_id IN (SELECT id FROM car_to_drivers WHERE vin = ?)", car.VIN)
	if err != nil {
		return nil, errors.WithStack(err)
	}

	_, err = tx.Model(&common.CarSetting{}).Where("vin = ?", car.VIN).Delete()
	if err != nil {
		return nil, errors.WithStack(err)
	}

	_, err = tx.Model(&common.CarToDriver{VIN: car.VIN}).Where("vin = ?vin").Delete()
	if err != nil {
		return nil, errors.WithStack(err)
	}

	_, err = tx.Model(&common.CarECU{VIN: car.VIN}).Where("vin = ?vin").Delete()
	if err != nil {
		return nil, errors.WithStack(err)
	}

	_, err = tx.Exec("DELETE FROM car_update_statuses WHERE car_update_id IN (SELECT id FROM car_updates WHERE vin = ?)", car.VIN)
	if err != nil {
		return nil, errors.WithStack(err)
	}

	_, err = tx.Model(&common.CarUpdate{VIN: car.VIN}).Where("vin = ?vin").Delete()
	if err != nil {
		return nil, errors.WithStack(err)
	}

	_, err = tx.Model(&common.CarVersionLogs{VIN: car.VIN}).Where("vin = ?vin").Delete()
	if err != nil {
		return nil, errors.WithStack(err)
	}

	result, err := tx.Model(car).WherePK().Delete()
	if err != nil {
		return nil, errors.WithStack(err)
	}

	err = tx.Commit()

	return result, err
}

func (c *Cars) Update(car *common.Car) (orm.Result, error) {
	if car.VIN == "" {
		return nil, errors.WithStack(&validator.FieldError{
			ErrorMsg: "VIN required",
		})
	}

	return c.resultWithStack(c.GetDBConn().Model(car).Column("iccid", "model", "year", "trim", "country", "powertrain", "restraint", "body_type", "tags", "sums_version").WherePK().Update())
}

// UpdateICCID should be merged with the Update method
func (c *Cars) UpdateICCID(car *common.Car) (orm.Result, error) {
	if car.VIN == "" {
		return nil, errors.WithStack(&validator.FieldError{
			ErrorMsg: "VIN required",
		})
	}

	// Catch no-op where the iccid is updated unnecessarily 
	result, err := c.GetDBConn().Model(car).
		Column("iccid").
		Where("vin = ? AND iccid != ?", car.VIN, car.ICCID).Update()

	return result, errors.WithStack(err)
}

func (c *Cars) UpdateSoldStatus(car *common.Car) (orm.Result, error) {
	if car.VIN == "" {
		return nil, errors.WithStack(&validator.FieldError{
			ErrorMsg: "VIN required",
		})
	}

	result, err := c.GetDBConn().Model(car).
		Column("sold_status").
		Where("vin = ?", car.VIN).Update()

	return result, errors.WithStack(err)
}

func (c *Cars) Insert(car *common.Car) (orm.Result, error) {
	car.VIN = strings.ToUpper(car.VIN)
	return c.insert(car)
}

func (c *Cars) Load(car *common.Car) error {
	query := c.GetDBConn().Model(car)

	if car.VIN != "" {
		query.WherePK()
	} else {
		return errors.New("no VIN")
	}

	err := query.Select()
	if err != nil {
		return errors.WithStack(err)
	}

	drivers, err := c.GetDrivers(car.VIN)
	if err != nil {
		return err
	}
	car.Drivers = drivers

	return nil
}

func (c *Cars) Count(filter *common.Car) (int, error) {
	query := c.GetDBConn().Model((*common.Car)(nil))
	c.selectFilter(query, filter)

	return c.countWithStack(query.Count())
}

func (c *Cars) AddDriver(car *common.Car, driver *common.Driver, role string) (*common.CarToDriver, error) {
	if car.VIN == "" {
		return nil, errors.New("car needs insert")
	}
	if driver.ID == "" {
		return nil, errors.New("driver needs insert")
	}
	rel := common.CarToDriver{
		VIN:        car.VIN,
		DriverID:   driver.ID,
		DriverRole: role,
	}
	result, err := c.GetDBConn().Model(&rel).Column("driver_role").Where("vin = ?vin AND driver_id = ?driver_id").Update()

	if err != nil {
		return nil, errors.WithStack(err)
	}
	if result.RowsAffected() == 1 {
		return &rel, nil
	}

	_, err = c.GetDBConn().Model(&rel).Insert()

	return &rel, errors.WithStack(err)
}

// RemoveDriver removes driver to the car
func (c *Cars) RemoveDriver(vin string, driverID string) (orm.Result, error) {
	if vin == "" {
		return nil, errors.New("vin required")
	}

	if driverID == "" {
		return nil, errors.New("driver_id required")
	}

	rel := common.CarToDriver{
		VIN:      vin,
		DriverID: driverID,
	}

	// select the CarToDriver
	err := c.GetDBConn().Model(&rel).ColumnExpr("id").Where("vin = ?vin AND driver_id = ?driver_id").Select()
	if err != nil {
		return nil, errors.WithStack(err)
	}

	total := &ORMResults{}
	tx, err := c.GetDBConn().Begin()
	if err != nil {
		return total, errors.WithStack(err)
	}
	defer func() {
		if err != nil {
			tx.Rollback()
		}
		tx.Close()
	}()

	// delete any related subscriptions before deleting the CarToDriver
	result, err := tx.Model(&common.Subscription{}).Where("car_to_driver_id = ?", rel.ID).Delete()
	if err != nil {
		return total, errors.WithStack(err)
	}
	total.AddResult(result)

	// now delete the CarToDriver
	result, err = tx.Model(&rel).Where("vin = ?vin AND driver_id = ?driver_id").Delete()
	if err != nil {
		return total, errors.WithStack(err)
	}
	total.AddResult(result)

	err = tx.Commit()
	if err != nil {
		return total, errors.WithStack(err)
	}

	return total, nil
}

// SelectCarToDriver queries CarToDrivers according to the filters
func (c *Cars) SelectCarToDriver(filter *common.CarToDriver) ([]common.CarToDriver, error) {
	drivers := []common.CarToDriver{}
	query := c.GetDBConn().Model(&drivers)

	if filter.VIN != "" {
		query.Where("vin = ?", filter.VIN)
	}

	if filter.DriverID != "" {
		query.Where("driver_id = ?", filter.DriverID)
	}

	err := query.Relation("Subscriptions").Select()
	if err != nil {
		return drivers, errors.WithStack(err)
	}

	err = c.addSettings(drivers)
	if err != nil {
		return nil, err
	}

	return drivers, nil
}

// GetDrivers gets drivers for the car
func (c *Cars) GetDrivers(vin string) ([]common.CarToDriver, error) {
	if vin == "" {
		return nil, errors.New("car required")
	}

	cardrivers := []common.CarToDriver{}
	err := c.GetDBConn().Model(&cardrivers).Where("vin = ?", vin).Select()
	if err != nil {
		return nil, errors.WithStack(err)
	}

	err = c.addSettings(cardrivers)
	if err != nil {
		return nil, err
	}

	return cardrivers, err
}

func (c *Cars) addSettings(car2dr []common.CarToDriver) error {
	if len(car2dr) == 0 {
		return nil
	}

	var settings []common.CarSetting
	qSettings := c.GetDBConn().Model(&settings)

	indexMap := make(map[string]map[string]int, len(car2dr))
	for k, d := range car2dr {
		qSettings.WhereOr("vin = ? AND driver_id = ?", d.VIN, d.DriverID)
		e, ok := indexMap[d.VIN]
		if !ok {
			indexMap[d.VIN] = map[string]int{d.DriverID: k}
			continue
		}

		e[d.DriverID] = k
	}

	err := qSettings.Select()
	if err != nil {
		return errors.WithStack(err)
	}

	for _, sett := range settings {
		idxs := indexMap[sett.VIN]
		if sett.DriverID == "" {
			for _, idx := range idxs {
				car2dr[idx].Settings = append(car2dr[idx].Settings, sett)
			}

			continue
		}

		idx, ok := idxs[sett.DriverID]
		if !ok {
			return errors.WithStack(errors.New("unexpected index; consider code changes"))
		}

		car2dr[idx].Settings = append(car2dr[idx].Settings, sett)
	}

	return nil
}

func (c *Cars) GetTRexSetting(vin string) (common.TRexSetting, error) {
	tRexSetting := common.TRexSetting{}
	if vin == "" {
		return tRexSetting, errors.New("car required")
	}

	err := c.GetDBConn().Model(&tRexSetting).Where("vin = ?", vin).Select()

	return tRexSetting, errors.WithStack(err)
}

func (c *Cars) GetModels() ([]string, error) {
	models := []string{}
	_, err := c.GetDBConn().Query(&models, "SELECT model from view_car_models ORDER BY model")
	return models, errors.WithStack(err)
}

func (c *Cars) GetYears() ([]int, error) {
	years := []int{}
	_, err := c.GetDBConn().Query(&years, "SELECT year from view_car_years ORDER BY year")
	return years, errors.WithStack(err)
}

func (c *Cars) SetSetting(setting *common.CarSetting) (orm.Result, error) {
	// TODO: Refactor to make only one call
	// _, err := db.Model(book).
	// OnConflict("(id) DO UPDATE").
	// Set("title = EXCLUDED.title").
	// Insert()

	existing := common.CarSetting{
		VIN:      setting.VIN,
		DriverID: setting.DriverID,
		Name:     setting.Name,
	}
	err := c.GetDBConn().Model(&existing).Where(c.getSettingsWhere(setting)).Select()
	if err != nil {
		if errors.Is(err, pg.ErrNoRows) {
			return c.insert(setting)
		}

		return nil, err
	}

	return c.updateSetting(setting)
}

func (c *Cars) getSettingsWhere(setting *common.CarSetting) string {
	where := "vin = ?vin and driver_id = ?driver_id and name = ?name"
	if setting.DriverID == "" {
		where = "vin = ?vin and (driver_id IS NULL) and name = ?name"
	}
	return where
}

func (c *Cars) updateSetting(setting *common.CarSetting) (orm.Result, error) {
	return c.resultWithStack(c.GetDBConn().Model(setting).Column("value").
		Where(c.getSettingsWhere(setting)).Update())
}

func (c *Cars) GetSettings(driver *common.CarToDriver) ([]common.CarSetting, error) {
	settings := []common.CarSetting{}
	err := c.GetDBConn().Model(&settings).Where("vin = ? AND (driver_id = ? OR driver_id IS NULL)", driver.VIN, driver.DriverID).Select()
	if err != nil {
		return nil, errors.WithStack(err)
	}
	return settings, nil
}

func (c *Cars) GetVehicleSpecificSettings(driver *common.CarToDriver) ([]common.CarSetting, error) {
	settings := []common.CarSetting{}
	query := c.GetDBConn().
		Model(&settings).
		Where("vin = ?", driver.VIN)
	err := query.Select()

	if err != nil {
		return nil, errors.WithStack(err)
	}
	return settings, nil
}

func (c *Cars) DeleteSetting(setting *common.CarSetting) (orm.Result, error) {
	return c.resultWithStack(c.GetDBConn().Model(&setting).Where("vin = ?vin AND driver_id = ?driver_id AND name = ?name").Delete())
}

func (c *Cars) UpdateCarECU(ecu *common.CarECU) error {
	result, err := c.GetDBConn().Model(ecu).Where("vin = ?vin AND ecu = ?ecu").Column("version", "fingerprint", "code_data_string", "serial_number", "hw_version", "boot_loader_version", "vendor", "supplier_sw_version").Update()
	if err != nil {
		return errors.WithStack(err)
	}
	if result.RowsAffected() == 1 {
		return nil
	}

	_, err = c.GetDBConn().Model(ecu).Insert()

	return errors.WithStack(err)
}

func (c *Cars) UpdateCarECUs(ecus []common.CarECU) error {
	query := c.GetDBConn().
		Model(&ecus).
		OnConflict("(vin, ecu) DO UPDATE").
		Set("version = EXCLUDED.version").
		Set("serial_number = COALESCE(EXCLUDED.serial_number, car_ecu.serial_number)").
		Set("hw_version = COALESCE(EXCLUDED.hw_version, car_ecu.hw_version)").
		Set("boot_loader_version = COALESCE(EXCLUDED.boot_loader_version, car_ecu.boot_loader_version)").
		Set("fingerprint = COALESCE(EXCLUDED.fingerprint, car_ecu.fingerprint)").
		Set("code_data_string = COALESCE(EXCLUDED.code_data_string, car_ecu.code_data_string)").
		Set("vendor = COALESCE(EXCLUDED.vendor, car_ecu.vendor)").
		Set("supplier_sw_version = COALESCE(EXCLUDED.supplier_sw_version, car_ecu.supplier_sw_version)")

	_, err := query.Insert()
	if err != nil {
		return errors.WithStack(err)
	}

	return nil
}

func (c *Cars) InsertCarECUs(ecus []common.CarECU) error {
	var err error
	var tx *pg.Tx
	conn := c.client.GetConn()
	tx, err = conn.Begin()
	if err != nil {
		return errors.WithStack(err)
	}
	defer func() {
		if err != nil {
			txerr := tx.Rollback()
			if txerr != nil {
				logger.Error().Err(errors.WithStack(txerr)).Send()
			}
		}
	}()
	for _, ecu := range ecus {
		onConflict := "(coalesce(vin, 'null'), coalesce(ecu, 'null'), coalesce(version, 'null'), coalesce(serial_number, 'null'), coalesce(hw_version, 'null'), coalesce(boot_loader_version, 'null'), coalesce(fingerprint, 'null'), coalesce(code_data_string, 'null'), coalesce(vendor, 'null'), coalesce(supplier_sw_version, 'null'), coalesce(assy_number, 'null')) DO UPDATE SET updated_at = EXCLUDED.updated_at"
		_, err = tx.Model(&ecu).OnConflict(onConflict).Insert()
		if err != nil {
			return errors.WithStack(err)
		}
	}

	err = errors.WithStack(tx.Commit())
	return err
}

// car_ecus table is also a log of the ecu versions of a car. Use the .Unique on filter to
func (c *Cars) GetCarECUs(filter common.CarECUFilter, paging *PageQueryOptions) ([]common.CarECU, error) {
	ecus := []common.CarECU{}
	query := c.GetDBConn().Model(&ecus)

	c.selectECUsFilter(query, filter)
	c.pageQuery(query, paging)
	err := query.Select()

	return ecus, errors.WithStack(err)
}

func (c *Cars) selectECUsFilter(query *orm.Query, filter common.CarECUFilter) {
	if filter.Unique {
		query.DistinctOn("vin, ecu")
		query.Order("vin", "ecu", "updated_at DESC")
	}

	if filter.VIN != "" {
		query.Where("vin = ?", filter.VIN)
	}

	if len(filter.ECUs) > 0 {
		query.Where("ecu in (?)", pg.In(filter.ECUs))
	}

	if filter.Search != "" {
		query.Where("document @@ plainto_tsquery(?)", filter.Search)
	}

	if filter.FlashPackNumberExist {
		query.Where("flashpack_number IS NOT null")
	}

	if filter.HWVersionRequired {
		query.Where("hw_version IS NOT null")
	}
}

func (c *Cars) GetCarECUsCount(filter common.CarECUFilter) (int, error) {
	query := c.GetDBConn().Model((*common.CarECU)(nil))
	c.selectECUsFilter(query, filter)
	return c.countWithStack(query.Count())
}

func (c *Cars) UpdateCarFlashpackVersion(vin string, flashpack string) (orm.Result, error) {
	if vin == "" {
		return nil, errors.WithStack(&validator.FieldError{
			ErrorMsg: "VIN required",
		})
	}

	car := &common.Car{VIN: vin, Flashpack: flashpack}

	return c.resultWithStack(c.GetDBConn().Model(car).Column("flashpack").WherePK().Update())
}

func (c *Cars) GetFlashpackVersions(carModel string, carTrim string, carYear int, options *PageQueryOptions) ([]common.CarFlashpackVersionResponse, error) {
	var flashpacks = []common.CarFlashpackVersionResponse{}

	model := []common.CarFlashpackVersion{}
	q := c.GetDBConn().Model(&model).
		DistinctOn("flashpack").
		Where("car_model = ?", carModel).
		Where("car_trim = ?", carTrim).
		Where("car_year = ?", carYear)

	c.pageQuery(q, options)

	err := q.Select()
	if err == nil {
		for _, m := range model {
			flashpacks = append(flashpacks, common.CarFlashpackVersionResponse{
				Flashpack: m.Flashpack,
				CarTrim:   m.CarTrim,
				CarModel:  m.CarModel,
				CarYear:   m.CarYear,
			})
		}

		return flashpacks, nil
	} else if !errors.Is(err, pg.ErrNoRows) {
		return flashpacks, errors.WithStack(err)
	}

	return flashpacks, nil
}

func (c *Cars) GetFlashpackVersionsCount(carModel string, carTrim string, carYear int) (int, error) {
	query := c.GetDBConn().Model(&common.CarFlashpackVersion{}).
		DistinctOn("flashpack").
		Where("car_model = ?", carModel).
		Where("car_trim = ?", carTrim).
		Where("car_year = ?", carYear)

	return query.Count()
}

func (c *Cars) GetNextFlashpackVersion(carModel string, carTrim string, flashpack string) (*common.CarFlashpackVersionResponse, error) {
	var flashpacks = []common.CarFlashpackVersionResponse{}

	model := []common.CarFlashpackVersion{}
	q := c.GetDBConn().Model(&model).
		DistinctOn("flashpack").
		Where("car_model = ?", carModel).
		Where("car_trim = ?", carTrim)

	err := q.Select()
	if err == nil {
		// sort the flashpack numbers in descending order
		for _, c := range model {
			flashpacks = append(flashpacks, common.CarFlashpackVersionResponse{
				Flashpack: c.Flashpack,
				CarModel:  c.CarModel,
				CarYear:   c.CarYear,
			})
		}
		sort.Slice(flashpacks, func(i, j int) bool {
			iFp, _ := strconv.ParseFloat(flashpacks[i].Flashpack, 64) // guaranteed numeric
			jFp, _ := strconv.ParseFloat(flashpacks[j].Flashpack, 64) // guaranteed numeric
			iYear := flashpacks[i].CarYear
			jYear := flashpacks[j].CarYear

			if iYear == jYear {
				return iFp > jFp
			} else {
				return iYear > jYear
			}
		})

		// get the next flashpack in order
		for i, f := range flashpacks {
			if f.Flashpack == flashpack && i > 0 {
				return &flashpacks[i-1], nil
			}
		}
	} else if !errors.Is(err, pg.ErrNoRows) {
		return nil, errors.WithStack(err)
	}

	return nil, nil
}

func (c *Cars) DeleteFlashpackVersion(carModel string, carTrim string, carYear int, flashpack string) error {
	var err error
	var tx *pg.Tx
	conn := c.client.GetConn()
	tx, err = conn.Begin()
	if err != nil {
		return errors.WithStack(err)
	}
	defer func() {
		if err != nil {
			txerr := tx.Rollback()
			if txerr != nil {
				logger.Error().Err(errors.WithStack(txerr)).Send()
			}
		}
	}()
	_, err = tx.Model(&common.CarFlashpackVersion{}).
		Where("flashpack = ? AND car_model = ? AND car_trim = ? AND car_year = ?", flashpack, carModel, carTrim, carYear).
		Delete()
	if err != nil {
		return errors.WithStack(err)
	}

	err = errors.WithStack(tx.Commit())
	return err
}

func (c *Cars) GetCarFlashpackVersionMappingsByModelTrim(carModel string, carTrim string, options *PageQueryOptions) ([]common.CarFlashpackVersion, error) {
	model := []common.CarFlashpackVersion{}
	q := c.GetDBConn().Model(&model).
		Where("car_model = ?", carModel).
		Where("car_trim = ?", carTrim)

	c.pageQuery(q, options)

	err := q.Select()
	if err == nil {
		return model, nil
	} else if !errors.Is(err, pg.ErrNoRows) {
		return model, errors.WithStack(err)
	}

	return model, nil
}

func (c *Cars) GetCarFlashpackVersionMappingsByModelTrimYearFlashpack(carModel string, carTrim string, carYear int, flashpack string, options *PageQueryOptions) ([]common.CarFlashpackVersion, error) {
	model := []common.CarFlashpackVersion{}
	q := c.GetDBConn().Model(&model).
		Where("car_model = ?", carModel).
		Where("car_trim = ?", carTrim).
		Where("car_year = ?", carYear).
		Where("flashpack = ?", flashpack)

	c.pageQuery(q, options)

	err := q.Select()
	if err == nil {
		return model, nil
	} else if !errors.Is(err, pg.ErrNoRows) {
		return model, errors.WithStack(err)
	}

	return model, nil
}

func (c *Cars) GetCarFlashpackVersionMappingsByModelTrimYearFlashpackCount(carModel string, carTrim string, carYear int, flashpack string) (int, error) {
	model := []common.CarFlashpackVersion{}
	q := c.GetDBConn().Model(&model).
		Where("car_model = ?", carModel).
		Where("car_trim = ?", carTrim).
		Where("car_year = ?", carYear).
		Where("flashpack = ?", flashpack)

	return q.Count()
}

func (c *Cars) GetCarECUNamesByModelTrim(carModel string, carTrim string) ([]string, error) {
	model := []common.CarECU{}
	q := c.GetDBConn().Model(&model).
		DistinctOn("car_ecu.ecu").
		Join("INNER JOIN cars").
		JoinOn("car_ecu.vin = cars.vin").
		Where("cars.model = ?", carModel).
		Where("cars.trim = ?", carTrim).
		Where("car_ecu.ecu ~ '^[a-zA-Z_]+$'").
		Order("car_ecu.ecu ASC")

	err := q.Select()
	if err == nil {
		var ecuNames []string

		for _, c := range model {
			ecuNames = append(ecuNames, c.ECU)
		}

		return ecuNames, nil
	} else if errors.Is(err, pg.ErrNoRows) {
		return nil, nil
	}
	return nil, errors.WithStack(err)
}

func (c *Cars) AddCarFlashpackVersionMappings(carFlashpackVersions []common.CarFlashpackVersion) error {
	var err error
	var tx *pg.Tx
	conn := c.client.GetConn()
	tx, err = conn.Begin()
	if err != nil {
		return errors.WithStack(err)
	}
	defer func() {
		if err != nil {
			txerr := tx.Rollback()
			if txerr != nil {
				logger.Error().Err(errors.WithStack(txerr)).Send()
			}
		}
	}()
	for _, v := range carFlashpackVersions {
		_, err = tx.Model(&v).
			Where("flashpack = ? AND car_model = ? AND car_year = ? AND car_ecu_name = ? AND car_ecu_version = ?", v.Flashpack, v.CarModel, v.CarYear, v.CarECUName, v.CarECUVersion).
			SelectOrInsert()
		if err != nil {
			return errors.WithStack(err)
		}
	}

	err = errors.WithStack(tx.Commit())
	return err
}

func (c *Cars) GetCarsForDriver(driverID string) ([]common.CarToDriver, error) {
	items := []common.CarToDriver{}
	query := c.GetDBConn().
		Model(&items).
		Where("driver_id = ?", driverID)
	err := query.Select()

	if err != nil {
		return nil, errors.WithStack(err)
	}

	return items, nil
}

func (c *Cars) UpdateBLEKey(vin string, driverid string, blekey string) (externalUser common.DriverExternal, err error) {
	item := common.CarToDriver{
		VIN:      vin,
		DriverID: driverid,
		BLEKey:   blekey,
	}

	result, err := c.GetDBConn().Model(&item).Column("ble_key").Where("vin = ?vin AND driver_id = ?driver_id").Update()
	if err != nil {
		return externalUser, errors.WithStack(err)
	}

	if result.RowsAffected() == 0 {
		return externalUser, errors.Errorf("cannot update ble_key. %s and %s does not exist", vin, driverid)
	}

	// This was kind of dumb
	query := `SELECT external_id, source FROM drivers_external WHERE fisker_id = ?`
	_, err = c.GetDBConn().QueryOne(&externalUser, query, driverid)
	if err == pg.ErrNoRows {
		err = nil
	}

	return externalUser, nil
}

// Should add ECU's to this as well, in the future
func (c *Cars) GetSoftwareVersion(vin string)(result common.CarPKCOSVersion, err error){
	query := `
		SELECT cars.sums_version,
		sums_versions.os_version
		FROM public.cars
		JOIN sums_versions ON cars.sums_version = sums_versions.version
		WHERE cars.vin = ?`
	_, err = c.GetDBConn().Query(&result, query, vin)
	if err != nil {
		return
	}
	return
}

func (c *Cars) GetSoftwareAndPKCVersions(vins []string) (results []common.CarPKCOSVersion, err error) {
	query := `
			SELECT 
			cars.vin,
			car_pkcs.version AS pkc_version,
			cars.sums_version,
			sums_versions.os_version
			FROM public.cars
			JOIN sums_versions ON cars.sums_version = sums_versions.version
			JOIN (
			SELECT DISTINCT ON (vin, ecu) vin, version
			FROM public.car_ecus
			WHERE ecu = 'PKC' AND vin IN (?)
			ORDER BY vin, ecu, created_at DESC
			) AS car_pkcs ON cars.vin = car_pkcs.vin
			WHERE cars.vin IN (?);`
	_, err = c.GetDBConn().Query(&results, query, pg.In(vins), pg.In(vins))
	if err != nil {
		return
	}
	// for _, entry := range results{
	// 	res[entry.Vin] = common.CarStateAL{
	// 		OSVersion: entry.OSVersion,
	// 		SumsVersion: entry.SumsVersion,
	// 		PKCVersion: entry.PKCVersion,
	// 	}
	// }

	return
}

func (c *Cars) GetWhiteListCars()(vins []string, err error){
	query := `SELECT vin FROM cars_whitelist`
	_, err = c.GetDBConn().Query(&vins, query)
	if errors.Is(err, pg.ErrNoRows) {
		err = nil
	}
	return
}

func (c *Cars) WhitelistCars(vins []string, source string)(err error){
	// This is a really dumb way to do this, but not sure how the package supports multiple inserts like this
	query := `INSERT into cars_whitelist (vin, source) VALUES (?, ?) ON CONFLICT DO NOTHING`
	for _, vin := range vins {
		_, tempErr := c.GetDBConn().Exec(query, vin, source)
		if tempErr != nil {
			tempErr = errors.New(fmt.Sprintf("%s - VIN: %s", tempErr.Error(), vin))
			err = errors0.Join(err, tempErr)
		}
	}
	return
}

func (c *Cars) 	BlacklistCars(vins []string)(err error){
	query := `DELETE FROM cars_whitelist WHERE vin IN (?)`
	_, err = c.GetDBConn().Exec(query, pg.In(vins))
	return
}

// May want to change this so it returns the VIN along with the ICCID
func (c *Cars) GetICCIDs(vins []string)(iccids []string, err error){
	query := `SELECT iccid FROM cars WHERE vin IN (?)`
	_, err = c.GetDBConn().Query(&iccids, query, pg.In(vins))
	if err != nil {
		return
	}
	return
}