package services

import (
	"sync"

	"github.com/fiskerinc/cloud-services/pkg/common"
	"github.com/fiskerinc/cloud-services/pkg/db"
	q "github.com/fiskerinc/cloud-services/pkg/db/queries"
	"github.com/fiskerinc/cloud-services/pkg/logger"
)

var (
	dbOnce     sync.Once
	dbInstance *DB
)

type DB struct {
	client                     *db.DBClient
	cars                       q.CarsInterface
	carVersionsLog             q.CarVersionsLogInterface
	carupdates                 q.CarUpdatesInterface
	filekeys                   q.FileKeysInterface
	manifests                  q.UpdateManifestsInterface
	ecu                        q.ECUInterface
	eccKeys                    q.EccKeysInterface
	ratePlan                   q.RatePlanInterface
	updateManifestSUMSVersions q.SUMSVersionsInterface
	carConfigData              q.CarConfigDataInterface

	onceEcu                        sync.Once
	onceClient                     sync.Once
	onceCars                       sync.Once
	onceCarVersionsLog             sync.Once
	onceCarUpdates                 sync.Once
	onceFileKeys                   sync.Once
	onceManifests                  sync.Once
	onceEccKeys                    sync.Once
	onceRatePlan                   sync.Once
	onceUpdateManifestSUMSVersions sync.Once
	onceCarConfigData              sync.Once

}

func GetDB() *DB {
	dbOnce.Do(func() {
		if dbInstance != nil {
			return
		}
		logger.Info().Msg("init DB instance")
		dbInstance = &DB{}
	})
	return dbInstance
}

func SetDB(db *DB) {
	if dbInstance != nil {
		dbInstance.Close()
	}
	dbInstance = db
}

func (d *DB) GetDBClient() *db.DBClient {
	d.onceClient.Do(func() {
		if d.client != nil {
			return
		}
		logger.Info().Msg("init DBClient instance")
		client := &db.DBClient{}
		client.RegisterManyToManyRel([]interface{}{
			(*common.CarToDriver)(nil),
		})
		err := client.InitSchema([]interface{}{
			(*common.UpdateManifest)(nil),
			(*common.Car)(nil),
			(*common.CarToDriver)(nil),
			(*common.CarUpdateStatus)(nil),
			(*common.CarUpdate)(nil),
			(*common.FileKey)(nil),
			(*common.RatePlanTMobile)(nil),
		})
		if err != nil {
			logger.Error().Err(err).Send()
		}
		// Uncomment below to show generated SQL queries
		// client.GetConn().AddQueryHook(db.SQLLogger{})
		d.client = client
	})
	return d.client
}

func (d *DB) SetDBClient(client *db.DBClient) {
	if d.client != nil {
		d.client.Close()
	}
	d.client = client
}

func (d *DB) Close() {
	if d.client == nil {
		return
	}
	d.client.Close()
}

func (d *DB) GetCarUpdates() q.CarUpdatesInterface {
	d.onceCarUpdates.Do(func() {
		if d.carupdates != nil {
			return
		}
		instance := &q.CarUpdates{}
		instance.SetClient(d.GetDBClient())
		d.carupdates = instance
	})
	return d.carupdates
}

func (d *DB) SetCarUpdates(carupdates q.CarUpdatesInterface) {
	d.carupdates = carupdates
}

func (d *DB) GetCars() q.CarsInterface {
	d.onceCars.Do(func() {
		if d.cars != nil {
			return
		}
		instance := &q.Cars{}
		instance.SetClient(d.GetDBClient())
		d.cars = instance
	})
	return d.cars
}

func (d *DB) SetCars(cars q.CarsInterface) {
	d.cars = cars
}

func (d *DB) GetCarVersionsLog() q.CarVersionsLogInterface {
	d.onceCarVersionsLog.Do(func() {
		if d.carVersionsLog != nil {
			return
		}
		instance := &q.CarVersionsLog{}
		instance.SetClient(d.GetDBClient())
		d.carVersionsLog = instance
	})
	return d.carVersionsLog
}

func (d *DB) SetCarVersionsLog(log q.CarVersionsLogInterface) {
	d.carVersionsLog = log
}

func (d *DB) GetFileKeys() q.FileKeysInterface {
	d.onceFileKeys.Do(func() {
		if d.filekeys != nil {
			return
		}
		instance := &q.FileKeys{}
		instance.SetClient(d.GetDBClient())
		d.filekeys = instance
	})
	return d.filekeys
}

func (d *DB) SetFileKeys(filekeys q.FileKeysInterface) {
	d.filekeys = filekeys
}

func (d *DB) GetUpdateManifests() q.UpdateManifestsInterface {
	d.onceManifests.Do(func() {
		if d.manifests != nil {
			return
		}
		instance := q.NewUpdateManifest(nil)
		instance.SetClient(d.GetDBClient())
		d.manifests = instance
	})
	return d.manifests
}
func (d *DB) GetECU() q.ECUInterface {

	d.onceEcu.Do(func() {
		if d.ecu != nil {
			return
		}
		instance := &q.ECU{}
		instance.SetClient(d.GetDBClient())
		d.ecu = instance
	})
	return d.ecu

}

func (d *DB) SetECU(instance q.ECUInterface) {
	d.ecu = instance
}

func (d *DB) SetManifests(manifests q.UpdateManifestsInterface) {
	d.manifests = manifests
}

func (d *DB) ModifyUpdateStatus(id int, status string) (*common.CarUpdate, error) {
	cu := d.GetCarUpdates()
	updates, err := cu.SelectByID(int64(id))
	if err != nil {
		return updates, err
	}

	updates.Status = status
	_, err = cu.UpdateStatus(updates)

	return updates, err
}

func (d *DB) GetECCKeys() q.EccKeysInterface {
	d.onceEccKeys.Do(func() {
		if d.eccKeys != nil {
			return
		}
		eccKeys := &q.EccKeys{}
		eccKeys.SetClient(d.GetDBClient())
		d.eccKeys = eccKeys
	})
	return d.eccKeys
}

func (d *DB) SetECCKeys(eccKeys q.EccKeysInterface) {
	d.eccKeys = eccKeys
}

func (d *DB) GetRatePlan() q.RatePlanInterface {
	d.onceRatePlan.Do(func() {
		if d.ratePlan != nil {
			return
		}
		instance := &q.RatePlanTmobile{}
		instance.SetClient(d.GetDBClient())
		d.ratePlan = instance
	})
	return d.ratePlan
}

func (d *DB) SetRatePlan(ratePlan q.RatePlanInterface) {
	d.ratePlan = ratePlan
}

func (d *DB) GetUpdateManifestSUMSVersions() q.SUMSVersionsInterface {
	d.onceUpdateManifestSUMSVersions.Do(func() {
		if d.updateManifestSUMSVersions != nil {
			return
		}
		instance := &q.SUMSVersions{}
		instance.SetClient(d.GetDBClient())
		d.updateManifestSUMSVersions = instance
	})
	return d.updateManifestSUMSVersions
}

func (d *DB) SetUpdateManifestVersions(umv q.SUMSVersionsInterface) {
	d.updateManifestSUMSVersions = umv
}

func (d *DB) GetCarConfigData() q.CarConfigDataInterface {
	d.onceCarConfigData.Do(func() {
		if d.carConfigData != nil {
			return
		}
		logger.Debug().Msg("Init CarConfigData instance")
		carConfigData := &q.CarConfigData{}
		carConfigData.SetClient(d.GetDBClient())
		d.carConfigData = carConfigData
	})
	return d.carConfigData
}

func (d *DB) SetCarConfigData(carConfigData q.CarConfigDataInterface) {
	d.carConfigData = carConfigData
}
