// prepares bulk operations to be run successfully
package bulk

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

// BulkRecord contains important information
// about the corresponding bulk operation
type BulkRecord struct {
	Filename   string
	Name       string
	Content    []byte
	Complete   bool
	NextRow    int
	ExecutedAt time.Time
}

// RunSingleBulk executed a specified bulk operation on the database
func RunSingleBulk(db *sql.DB, driverQueries queries.DriverQueries, bulkRecord *BulkRecord) error {
	dbBulk, err := queries.GetSingleDBBulk(db, driverQueries, bulkRecord.Filename)
	if err != nil {
		fmt.Println("RUN SINGLE BULK ", err)
		return err
	}

	if dbBulk != nil && dbBulk.Complete {
		fmt.Printf("Operation %s complete\n", bulkRecord.Filename)
		return nil
	}

	if dbBulk != nil {
		bulkRecord.NextRow = dbBulk.NextRow
		bulkRecord.Complete = dbBulk.Complete
		bulkRecord.ExecutedAt = dbBulk.ExecutedAt
	}

	return ApplyBulk(db, driverQueries, bulkRecord)
}

// RunBulk executes all bulk operations in the specified directory on the database
func RunBulk(db *sql.DB, driverQueries queries.DriverQueries, bulkDir string) error {
	toExecute := make([]*BulkRecord, 0)

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

	fileBulk, err := GetBulkFromDir(bulkDir)
	if err != nil {
		return err
	}

	for _, bulk := range fileBulk {
		if dbRecord, ok := (*dbBulk)[bulk.Filename]; ok {
			bulk.Complete = dbRecord.Complete
			bulk.ExecutedAt = dbRecord.ExecutedAt
			if !bulk.Complete {
				bulk.NextRow = dbRecord.NextRow
				toExecute = append(toExecute, bulk)
			}
		} else {
			toExecute = append(toExecute, bulk)
		}
	}
	// log.Info(toExecute)

	if len(toExecute) == 0 {
		fmt.Println("All bulk operations are finished.")
		return nil
	}

	for _, bulk := range toExecute {
		err = ApplyBulk(db, driverQueries, bulk)
		if err != nil {
			return err
		}
	}
	return nil
}

// 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
}

// GetBulkFromDir takes a look inside the specified directory
// and returns all the bulk ops as an array of BulkRecords.
func GetBulkFromDir(path string) ([]*BulkRecord, error) {
	b := make([]*BulkRecord, 0)

	exists, err := directoryExists(path)
	if err != nil {
		return b, err
	}
	if !exists {
		return b, nil
	}

	err = filepath.Walk(path, func(name string, info os.FileInfo, e error) error {
		if !strings.HasSuffix(info.Name(), ".yaml") {
			return nil
		}
		data, e := ioutil.ReadFile(filepath.Join(path, info.Name()))
		if e != nil {
			return e
		}
		b = append(b, &BulkRecord{
			Filename:   info.Name(),
			Name:       extractNameFromFile(info.Name()),
			Content:    data,
			Complete:   false,
			NextRow:    0,
			ExecutedAt: time.Time{},
		})
		return nil
	})

	if err != nil {
		return b, err
	}

	return b, nil
}

// helper func to get name of migration from filename
func extractNameFromFile(name string) string {
	s := strings.Split(name, "_")
	s[len(s)-1] = strings.Split(s[len(s)-1], ".")[0]

	return strings.Join(s[1:], " ")
}

// CreateBulk create a new bulk yaml file in the appropriate directory.
func CreateBulk(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)

	template := bulkTemplate

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

	return
}

// bulk template
var bulkTemplate = template.Must(template.New("iceman.bulk").Parse(`
relation: 

read:
  query: >
    
  start_id: 0

write:
  query: >
    
  max_writes: 

batch:
  size: 1000
  batch: false

sleep: 0

verbose: false

`))
