package services

import (
	"sync"

	"github.com/fiskerinc/cloud-services/pkg/common"
	"github.com/fiskerinc/cloud-services/pkg/common/actionlogger"
	"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
	actionLog                  q.ActionLogInterface
	cars                       q.CarsInterface
	ecckeys                    q.EccKeysInterface
	symKeys                    q.SymKeysInterface
	carupdates                 q.CarUpdatesInterface
	filekeys                   q.FileKeysInterface
	updatemanifest             q.UpdateManifestsInterface
	apiTokens                  q.APITokensInterface
	apiCalls                   q.APICallsInterface
	subpackages                q.SubscriptionPackagesInterface
	subfeatures                q.SubscriptionFeaturesInterface
	subconfigs                 q.SubscriptionConfigurationsInterface
	subscriptions              q.SubscriptionsInterface
	supplierAccounts           q.SupplierAccountsInterface
	issues                     q.IssuesInterface
	carVersionsLog             q.CarVersionsLogInterface
	updateManifestVersions     q.SUMSVersionsInterface
	swVersionRxSwin            q.SwVersionRxSwinInterface
	tags                       q.TagsInterface
	dtcecu                     q.ECUInterface
	carConfigData              q.CarConfigDataInterface
	driverEmails               q.DriverEmailsInterface
	onceActionLog              sync.Once
	onceDTCECU                 sync.Once
	onceIssues                 sync.Once
	onceClient                 sync.Once
	onceCars                   sync.Once
	onceCarUpdates             sync.Once
	onceFileKeys               sync.Once
	onceUpdateManifest         sync.Once
	onceAPITokens              sync.Once
	onceAPICalls               sync.Once
	onceSubPackages            sync.Once
	onceSubFeatures            sync.Once
	onceSubConfig              sync.Once
	onceSubscriptions          sync.Once
	onceSupplierForm           sync.Once
	onceCarVersionsLog         sync.Once
	onceUpdateManifestVersions sync.Once
	onceSwVersionRxSwin        sync.Once
	onceTags                   sync.Once
	onceECCKeys                sync.Once
	onceSymKeys                sync.Once
	onceCarConfigData          sync.Once
	onceDriverEmails           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{}
		err := client.InitSchema([]interface{}{
			(*actionlogger.ActionLog)(nil),
			(*common.SupplierAccount)(nil),
			(*common.APIToken)(nil),
			(*common.UpdateManifest)(nil),
			(*common.UpdateManifestECU)(nil),
			(*common.UpdateManifestFile)(nil),
			(*common.FileKey)(nil),
			(*common.ECCKeys)(nil),
			(*common.SymKeys)(nil),
			(*common.CarECU)(nil),
			(*common.Car)(nil),
			(*common.CarUpdate)(nil),
			(*common.Driver)(nil),
			(*common.CarToDriver)(nil),
			(*common.CarSetting)(nil),
			(*common.CarVersionLogs)(nil),
			(*common.SUMSVersion)(nil),
			(*common.SwVersionRxSwin)(nil),
		})
		if err != nil {
			logger.Error().Err(err).Send()
		}

		client.RegisterManyToManyRel([]interface{}{
			(*common.SubscriptionPackageToFeature)(nil),
		})
		d.client = client
	})

	// d.client.GetConn().AddQueryHook(db.SQLLogger{})
	return d.client
}

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

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

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

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

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

func (d *DB) GetECCKeys() q.EccKeysInterface {
	d.onceECCKeys.Do(func() {
		if d.ecckeys != nil {
			return
		}
		logger.Debug().Msg("Init ECCKeys instance")
		ecckeys := &q.EccKeys{}
		ecckeys.SetClient(d.GetDBClient())
		d.ecckeys = ecckeys
	})
	return d.ecckeys
}

func (d *DB) GetSymKeys() q.SymKeysInterface {
	d.onceSymKeys.Do(func() {
		if d.symKeys != nil {
			return
		}
		logger.Debug().Msg("Init SymKeys instance")
		symKeys := &q.SymKeys{}
		symKeys.SetClient(d.GetDBClient())
		d.symKeys = symKeys
	})
	return d.symKeys
}

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

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

func (d *DB) GetUpdateManifests() q.UpdateManifestsInterface {
	d.onceUpdateManifest.Do(func() {
		if d.updatemanifest != nil {
			return
		}
		logger.Debug().Msg("Init UpdateManifest instance")
		updatemanifest := q.NewUpdateManifest(nil)
		updatemanifest.SetClient(d.GetDBClient())
		d.updatemanifest = updatemanifest
	})
	return d.updatemanifest
}

func (d *DB) SetUpdateManifests(updatemanifest q.UpdateManifestsInterface) {
	d.updatemanifest = updatemanifest
}

func (d *DB) GetTags() q.TagsInterface {
	d.onceTags.Do(func() {
		if d.tags != nil {
			return
		}
		logger.Debug().Msg("Init Tags instance")
		tags := q.NewTags()
		tags.SetClient(d.GetDBClient())
		d.tags = tags
	})
	return d.tags
}

func (d *DB) SetTags(t q.TagsInterface) {
	d.tags = t
}

func (d *DB) GetAPITokens() q.APITokensInterface {
	d.onceAPITokens.Do(func() {
		if d.apiTokens != nil {
			return
		}
		logger.Debug().Msg("Init APITokens instance")
		apitokens := &q.APITokens{}
		apitokens.SetClient(d.GetDBClient())
		d.apiTokens = apitokens
	})
	return d.apiTokens
}

func (d *DB) SetAPITokens(apitokens q.APITokensInterface) {
	d.apiTokens = apitokens
}

func (d *DB) GetAPICalls() q.APICallsInterface {
	d.onceAPICalls.Do(func() {
		if d.apiCalls != nil {
			return
		}
		logger.Debug().Msg("Init APITokens instance")
		apiCalls := &q.APICalls{}
		apiCalls.SetClient(d.GetDBClient())
		d.apiCalls = apiCalls
	})
	return d.apiCalls
}

func (d *DB) SetAPICalls(apiCalls q.APICallsInterface) {
	d.apiCalls = apiCalls
}

func (d *DB) GetSubPackages() q.SubscriptionPackagesInterface {
	d.onceSubPackages.Do(func() {
		if d.subpackages != nil {
			return
		}
		logger.Debug().Msg("Init Subscription Packages instance")
		subpackages := &q.SubscriptionPackages{}
		subpackages.SetClient(d.GetDBClient())
		d.subpackages = subpackages
	})
	return d.subpackages
}

func (d *DB) SetSubPackages(instance q.SubscriptionPackagesInterface) {
	d.subpackages = instance
}

func (d *DB) GetSubFeatures() q.SubscriptionFeaturesInterface {
	d.onceSubFeatures.Do(func() {
		if d.subfeatures != nil {
			return
		}
		logger.Debug().Msg("Init Subscription Features instance")
		subfeatures := &q.SubscriptionFeatures{}
		subfeatures.SetClient(d.GetDBClient())
		d.subfeatures = subfeatures
	})
	return d.subfeatures
}

func (d *DB) SetSubFeatures(instance q.SubscriptionFeaturesInterface) {
	d.subfeatures = instance
}

func (d *DB) GetSubConfigurations() q.SubscriptionConfigurationsInterface {
	d.onceSubConfig.Do(func() {
		if d.subconfigs != nil {
			return
		}
		logger.Debug().Msg("Init Subscription Configurations instance")
		subconfigs := &q.SubscriptionConfigurations{}
		subconfigs.SetClient(d.GetDBClient())
		d.subconfigs = subconfigs
	})
	return d.subconfigs
}

func (d *DB) SetSubConfigurations(instance q.SubscriptionConfigurationsInterface) {
	d.subconfigs = instance
}

func (d *DB) GetSubscriptions() q.SubscriptionsInterface {
	d.onceSubscriptions.Do(func() {
		if d.subscriptions != nil {
			return
		}
		logger.Debug().Msg("Init Subscriptions instance")
		subscriptions := &q.Subscriptions{}
		subscriptions.SetClient(d.GetDBClient())
		d.subscriptions = subscriptions
	})
	return d.subscriptions
}

func (d *DB) SetSubscriptions(instance q.SubscriptionsInterface) {
	d.subscriptions = instance
}

func (d *DB) GetSupplierAccounts() q.SupplierAccountsInterface {
	d.onceSupplierForm.Do(func() {
		if d.supplierAccounts != nil {
			return
		}
		logger.Debug().Msg("Init SupplierForm instance")
		supplierForm := &q.SupplierAccounts{}
		supplierForm.SetClient(d.GetDBClient())
		d.supplierAccounts = supplierForm
	})
	return d.supplierAccounts
}

func (d *DB) SetSupplierAccount(accounts q.SupplierAccountsInterface) {
	d.supplierAccounts = accounts
}

func (d *DB) GetIssues() q.IssuesInterface {
	d.onceIssues.Do(func() {
		if d.issues != nil {
			return
		}
		instance := &q.Issues{}
		instance.SetClient(d.GetDBClient())
		d.issues = instance
	})
	return d.issues
}

func (d *DB) SetIssues(issues q.IssuesInterface) {
	d.issues = issues
}

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) GetUpdateManifestVersions() q.SUMSVersionsInterface {
	d.onceUpdateManifestVersions.Do(func() {
		if d.updateManifestVersions != nil {
			return
		}
		instance := &q.SUMSVersions{}
		instance.SetClient(d.GetDBClient())
		d.updateManifestVersions = instance
	})
	return d.updateManifestVersions
}

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

func (d *DB) GetSwVerRxSwin() q.SwVersionRxSwinInterface {
	d.onceSwVersionRxSwin.Do(func() {
		if d.swVersionRxSwin != nil {
			return
		}
		instance := &q.SwVersionRxSwin{}
		instance.SetClient(d.GetDBClient())
		d.swVersionRxSwin = instance
	})
	return d.swVersionRxSwin
}

func (d *DB) SetSwVersionRxSwin(svrs q.SwVersionRxSwinInterface) {
	d.swVersionRxSwin = svrs
}

func (d *DB) SetDTCECU(dtcecu q.ECUInterface) {
	d.dtcecu = dtcecu
}

func (d *DB) GetDTCECU() q.ECUInterface {
	d.onceDTCECU.Do(func() {
		if d.dtcecu != nil {
			return
		}
		logger.Debug().Msg("Init DTC ECU instance")
		dtcecu := &q.ECU{}
		dtcecu.SetClient(d.GetDBClient())
		d.dtcecu = dtcecu
	})
	return d.dtcecu
}

func (d *DB) GetActionLog() q.ActionLogInterface {
	d.onceActionLog.Do(func() {
		if d.actionLog != nil {
			return
		}
		instance := &q.ActionLogDB{}
		instance.SetClient(d.GetDBClient())
		d.actionLog = instance
	})
	return d.actionLog
}

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
}

func (d *DB) GetDriverEmails() q.DriverEmailsInterface {
	d.onceDriverEmails.Do(func() {
		if d.driverEmails != nil {
			return
		}
		logger.Debug().Msg("Init DriverEmails instance")
		driverEmails := &q.DriverEmails{}
		driverEmails.SetClient(d.GetDBClient())
		d.driverEmails = driverEmails
	})
	return d.driverEmails
}

func (d *DB) SetDriverEmails(driverEmails q.DriverEmailsInterface) {
	d.driverEmails = driverEmails
}

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