package status

import (
	"code.justin.tv/d8a/iceman/lib/dbconf"
	"code.justin.tv/d8a/iceman/lib/migrations"
	"code.justin.tv/d8a/iceman/lib/queries"
	"fmt"
	set "github.com/deckarep/golang-set"
	"math"
	"sort"
	"time"
)

type MigrationState int

const (
	CompletedState MigrationState = iota
	PendingState   MigrationState = iota
	MissingState   MigrationState = iota
)

type Status struct {
	Name    string
	Created time.Time
	Applied time.Time
	State   MigrationState
}

type Statuses []*Status

func (s Statuses) Len() int {
	return len(s)
}
func (s Statuses) Swap(i, j int) {
	s[i], s[j] = s[j], s[i]
}
func (s Statuses) Less(i, j int) bool {
	return s[i].Created.After(s[j].Created)
}

func OrganizeStatuses(conf *dbconf.DBConf) (Statuses, error) {
	db, err := dbconf.OpenDBFromDBConf(conf)
	if err != nil {
		return nil, err
	}
	defer queries.TryClose(db)

	// collect migrations in db
	driverQueries := conf.DriverQueries
	dbMigrations, err := queries.GetDBMigrations(db, driverQueries)
	if err != nil {
		return nil, err
	}

	allMigrationsInfo, err := queries.GetAllMigrationsInfo(db, driverQueries)
	if err != nil {
		return nil, err
	}

	// collect migrations in file
	fileMigrations, err := (&migrations.DirectoryMigrationSource{MigrationsDirectory: conf.Dir}).FetchAllMigrations()
	if err != nil {
		return nil, err
	}

	// add file migration names to set
	localMigrations := set.NewSet()
	for _, m := range fileMigrations {
		localMigrations.Add(m.Filename)
	}

	// intersect = applied = green
	applied := (dbMigrations).Intersect(localMigrations)

	// db - local = applied but not in local dir = red
	missing := (dbMigrations).Difference(localMigrations)

	// local - db = pending = yellow
	pending := (localMigrations).Difference(dbMigrations)

	// initialize slice of statuses
	var statuses Statuses

	// add pending migration info to allMigrationsInfo
	for _, m := range fileMigrations {
		if pending.Contains(m.Filename) {
			allMigrationsInfo[m.Filename] = &queries.MigrationInfo{
				m.Name,
				m.CreatedAt,
				time.Now(), // non-nil placeholder
			}
		}
	}

	for a := range applied.Iter() {
		statuses = append(statuses, &Status{
			allMigrationsInfo[fmt.Sprint(a)].Name,
			allMigrationsInfo[fmt.Sprint(a)].CreatedAt,
			allMigrationsInfo[fmt.Sprint(a)].AppliedAt,
			CompletedState,
		})
	}

	for m := range missing.Iter() {
		statuses = append(statuses, &Status{
			allMigrationsInfo[fmt.Sprint(m)].Name,
			allMigrationsInfo[fmt.Sprint(m)].CreatedAt,
			allMigrationsInfo[fmt.Sprint(m)].AppliedAt,
			MissingState,
		})
	}

	for p := range pending.Iter() {
		statuses = append(statuses, &Status{
			allMigrationsInfo[fmt.Sprint(p)].Name,
			allMigrationsInfo[fmt.Sprint(p)].CreatedAt,
			allMigrationsInfo[fmt.Sprint(p)].AppliedAt,
			PendingState,
		})
	}

	sort.Sort(statuses) // descending chronological order: applied time >> created time

	return statuses, nil
}

func GetMaxLength(statuses Statuses) int {
	maxNameLength := float64(9)
	for _, s := range statuses {
		maxNameLength = math.Max(float64(maxNameLength), float64(len(s.Name)))
	}
	max := int(maxNameLength)

	return max
}
