package bulk

import (
	"log"
	"time"

	"code.justin.tv/d8a/buddy/lib/config"
	"code.justin.tv/d8a/iceman/lib/bulk"
	"code.justin.tv/d8a/iceman/lib/dbconf"
	"code.justin.tv/d8a/iceman/lib/queries"
)

type StatusData struct {
	Cluster string
}
type StatusEntry struct {
	bulk.BulkRecord
	StatusData
}

type BulkRecordStore struct {
	cache           CachedBulkRecords
	lastRefreshTime map[string]time.Time
}

func NewBulkStore() *BulkRecordStore {
	return &BulkRecordStore{
		cache:           make(CachedBulkRecords),
		lastRefreshTime: make(map[string]time.Time),
	}
}

func (store *BulkRecordStore) AllStatuses() []*StatusEntry {
	statuses := []*StatusEntry{}

	for _, cluster := range store.cache {
		for _, status := range cluster {
			statuses = append(statuses, status)
		}
	}

	return statuses
}

func (store *BulkRecordStore) GetWorkableBulk(conf *config.ConfigFile) (*StatusEntry, error) {
	err := store.refreshOldestCluster(conf)
	if err != nil {
		return nil, err
	}

	oldestRecord := &StatusEntry{}

	for _, cluster := range store.cache {
		for _, bulkRecord := range cluster {
			if !bulkRecord.Complete && (oldestRecord.ExecutedAt.IsZero() || bulkRecord.ExecutedAt.Before(oldestRecord.ExecutedAt)) {
				oldestRecord = bulkRecord
			}
		}
	}

	if oldestRecord.Name == "" {
		log.Println("No uncompleted bulk found")
		return nil, nil
	}

	log.Println("Bulk found: ", oldestRecord.Name)
	return oldestRecord, nil
}

func (store *BulkRecordStore) refreshOldestCluster(conf *config.ConfigFile) error {
	oldest := time.Time{}
	oldestCluster := &config.Cluster{}

	for _, cluster := range conf.Cluster {
		lastUpdate, ok := store.lastRefreshTime[cluster.Name]
		if !ok {
			return store.refreshCluster(cluster)
		}

		if oldest.IsZero() || lastUpdate.Before(oldest) {
			oldest = lastUpdate
			oldestCluster = cluster
		}
	}

	if !oldest.IsZero() && oldest.Add(10*time.Minute).Before(time.Now()) {
		return store.refreshCluster(oldestCluster)
	}

	return nil
}

func (store *BulkRecordStore) refreshCluster(cluster *config.Cluster) error {
	log.Println("Refreshing ", cluster.Name)
	dbConf, err := cluster.GetIcemanDbConf("bulk")
	if err != nil {
		return err
	}

	store.lastRefreshTime[cluster.Name] = time.Now()
	return store.cache.RefreshFromDisk(cluster.Name, dbConf)
}

type CachedBulkRecords map[string]map[string]*StatusEntry

func (cache CachedBulkRecords) RefreshFromDisk(name string, conf *dbconf.DBConf) error {
	oldMap, ok := cache[name]
	if !ok {
		oldMap = make(map[string]*StatusEntry)
	}

	records, err := bulk.GetBulkFromDir(conf.Dir)
	if err != nil {
		return err
	}

	fileMap := make(map[string]*bulk.BulkRecord)
	for _, record := range records {
		fileMap[record.Name] = record
	}

	//We want to pick up bulk record file changes off disk- we need to add new records to the cache
	//we need to remove records from the cache that aren't on disk
	//if the content changed we need to replace the record & wipe progress in the DB
	allRecords := make(map[string]bool)
	for fileName := range fileMap {
		allRecords[fileName] = true
	}
	for cacheName := range oldMap {
		allRecords[cacheName] = true
	}

	//Update from file records
	for bulkRunName := range allRecords {
		_, cacheExists := oldMap[bulkRunName]
		_, fileExists := fileMap[bulkRunName]

		if !fileExists {
			delete(oldMap, bulkRunName)
		} else if !cacheExists || string(oldMap[bulkRunName].Content) != string(fileMap[bulkRunName].Content) {
			fileVal := fileMap[bulkRunName]
			oldMap[bulkRunName] = &StatusEntry{
				bulk.BulkRecord{
					Filename: fileVal.Filename,
					Name:     fileVal.Name,
					Content:  fileVal.Content,
				},
				StatusData{
					Cluster: name,
				},
			}
		}
	}

	//Pull db records
	db, err := dbconf.OpenDBFromDBConf(conf)
	if err != nil {
		return err
	}
	defer queries.TryClose(db)

	dbRecords, err := queries.GetDBBulk(db, conf.DriverQueries)
	if err != nil {
		return err
	}

	for bulkName, dbRecord := range *dbRecords {
		cacheRecord, ok := oldMap[bulkName]
		if ok {
			cacheRecord.Complete = dbRecord.Complete
			cacheRecord.NextRow = dbRecord.NextRow
			cacheRecord.ExecutedAt = dbRecord.ExecutedAt
		}
	}

	cache[name] = oldMap
	log.Println("Found ", len(cache), " records")
	return nil
}
