// prepares the migration to be executed successfully
package migrations

import (
	"code.justin.tv/d8a/iceman/lib/queries"
	"code.justin.tv/d8a/iceman/lib/util"
	"database/sql"
	"fmt"
	"os"
	"path/filepath"
	"text/template"
	"time"
)

// Migration Record contains important
// information about the migration
type MigrationRecord struct {
	Filename  string
	Name      string
	CreatedAt time.Time
	Content   []byte
}

// RunsMigrations runs the appropriate migrations in the local directory
// on the specified database. If the direction is true (up), then all
// unapplied migrations will be executed. Otherwise (the direction is down),
// the last applied migration will be rolled back. If the migration fails,
// a corresponding error will be returned. Otherwise, nil is returned.
func RunMigrations(db *sql.DB, driverQueries queries.DriverQueries, migrationSource MigrationSource, direction bool) ([]string, error) {
	names := make([]string, 0)
	var err error

	if direction { //up
		names, err = runUpMigration(migrationSource, db, driverQueries)
	} else { // down
		names, err = runDownMigration(migrationSource, db, driverQueries)
	}

	return names, err
}

// check if directory exists before traversing through
func directoryExists(path string) (bool, error) {
	_, err := os.Stat(path)
	if err == nil {
		return true, nil
	}
	if os.IsNotExist(err) {
		return false, nil
	}
	return true, err
}

// this is run in RunMigrations() for 'up'
func runUpMigration(migrationSource MigrationSource, db *sql.DB, driverQueries queries.DriverQueries) ([]string, error) {
	names := make([]string, 0)
	unappliedMigrations := make([]*MigrationRecord, 0)

	dbMigrations, err := queries.GetDBMigrations(db, driverQueries)
	if err != nil {
		return names, err
	}

	fileMigrations, err := migrationSource.FetchAllMigrations()
	if err != nil {
		return names, err
	}

	for _, migration := range fileMigrations {
		// query for file name in migration table
		if !(dbMigrations).Contains(migration.Filename) {
			unappliedMigrations = append(unappliedMigrations, migration)
		}
	}
	// log.Info(unappliedMigrations)

	if len(unappliedMigrations) == 0 {
		fmt.Println("No migrations to run. Up-to-date!")
		return names, nil
	}

	for _, m := range unappliedMigrations {
		err = ApplyMigration(db, driverQueries, m, true)

		if err != nil {
			return names, err
		}

		names = append(names, m.Name)
	}

	return names, nil
}

// this is run in RunMigrations() for 'down'
func runDownMigration(migrationSource MigrationSource, db *sql.DB, driverQueries queries.DriverQueries) ([]string, error) {
	names := make([]string, 0)

	name, err := queries.GetLastApplied(db, driverQueries)
	if err != nil {
		return names, err
	}

	if name == "" {
		fmt.Println("No migrations to rollback.")
		return names, nil
	}

	migration, err := migrationSource.FetchSingleMigration(name)
	if err != nil {
		return names, err
	}

	err = ApplyMigration(db, driverQueries, migration, false)
	if err != nil {
		return names, err
	}

	names = append(names, migration.Name)

	return names, nil
}

// CreateMigration creates a migration yaml file in the specified directory.
// The filename is a concatenation of the creation time and migration name
// joined by an underscore.
func CreateMigration(name, dir string, t time.Time) (path string, err error) {

	timestamp := t.Format("20060102150405")
	filename := fmt.Sprintf("%v_%v.%v", timestamp, name, "yaml")

	fpath := filepath.Join(dir, filename)

	path, err = util.WriteTemplateToFile(fpath, migrationTemplate, timestamp)

	return
}

// migration template
var migrationTemplate = template.Must(template.New("iceman.migration").Parse(
	`# YAML migration templates
# prefix list elements with a '-'. Operations: may have multiple elements, each containing
# queries, a txn boolean, and a timeout.  queries: may have multiple query elements

# Up is executed when this migration is applied.
up: 
  operations: 
    - queries:
        - <put queries here>
      txn: true
      timeout: <query timeout>
  timeout: 1000

# Down is executed when this migration is rolled back.
down: 
  operations: 
    - queries:
      - <put down queries here>
      txn: true
      timeout: <query timeout>
  timeout: 1000
`))
