package queries

import (
	"database/sql"
	"fmt"
	set "github.com/deckarep/golang-set"
	"time"
)

type MigrationInfo struct {
	Name      string
	CreatedAt time.Time
	AppliedAt time.Time
}

type DriverQueries interface {
	GetDataVersion() string
	CreateVersionsTable() string
	InsertVersion() string

	SetTimeout(milliseconds int) string
	CreateMigrationsTable() string
	InsertMigration() string
	DeleteMigration() string
	CreateBulkTable() string
	BulkTableV2() string
	InsertBulk() string
	UpdateBulk() string

	GetAllIds() string
	GetAllMigrations() string
	GetLastApplied() string
	GetAllBulks() string
	GetSingleBulk() string
	GetSingleBulkByFilename() string

	SelectIdBetween() string
	AndIdBetween() string

	FormatArray([]interface{}) []interface{}

	CreatePlaceholders(count int) string
}

// returns all migration names in table as a set
func GetDBMigrations(db *sql.DB, driverQueries DriverQueries) (set.Set, error) {
	migrationSet := set.NewSet()
	rows, err := db.Query(driverQueries.GetAllIds())

	if err != nil {
		return migrationSet, err
	}

	defer TryClose(rows)

	for rows.Next() {
		var id string
		err := rows.Scan(&id)
		if err != nil {
			return migrationSet, err
		}
		migrationSet.Add(id)
	}
	return migrationSet, nil
}

// returns all migration info in table as a set
func GetAllMigrationsInfo(db *sql.DB, driverQueries DriverQueries) (map[string]*MigrationInfo, error) {
	migrationMap := make(map[string]*MigrationInfo)
	rows, err := db.Query(driverQueries.GetAllMigrations())

	if err != nil {
		return migrationMap, err
	}
	defer TryClose(rows)

	for rows.Next() {
		var id string
		var name string
		var created_at time.Time
		var applied_at time.Time

		err := rows.Scan(&id, &name, &created_at, &applied_at)
		if err != nil {
			return migrationMap, err
		}
		// log.Info(id)
		migrationMap[id] = &MigrationInfo{
			name,
			created_at,
			applied_at,
		}
	}
	return migrationMap, nil
}

// return last applied migration (to help with 'down')
func GetLastApplied(db *sql.DB, driverQueries DriverQueries) (string, error) {
	var id string
	row, err := db.Query(driverQueries.GetLastApplied())

	if err != nil {
		return id, err
	}
	defer TryClose(row)

	for row.Next() {
		err := row.Scan(&id)
		if err != nil {
			return id, err
		}
	}
	return id, nil
}

type DatabaseBulkRecord struct {
	ID         string
	ExecutedAt time.Time
	Complete   bool
	NextRow    int
}

type multipleScanner interface {
	Scan(vars ...interface{}) error
}

func scanBulkRecord(scanner multipleScanner) (*DatabaseBulkRecord, error) {
	var id string
	var executedAt time.Time
	var complete bool
	var nextRow int
	err := scanner.Scan(&id, &executedAt, &complete, &nextRow)
	if err == sql.ErrNoRows {
		return nil, nil
	}
	if err != nil {
		return nil, err
	}

	return &DatabaseBulkRecord{
		ExecutedAt: executedAt,
		Complete:   complete,
		NextRow:    nextRow,
		ID:         id,
	}, nil
}

func GetSingleDBBulk(db *sql.DB, driverQueries DriverQueries, fileName string) (*DatabaseBulkRecord, error) {
	row := db.QueryRow(driverQueries.GetSingleBulkByFilename(), fileName)
	return scanBulkRecord(row)
}

// returns all bulk operation names and complete(?) in table as a set
func GetDBBulk(db *sql.DB, driverQueries DriverQueries) (*map[string]DatabaseBulkRecord, error) {
	bulks := make(map[string]DatabaseBulkRecord)
	rows, err := db.Query(driverQueries.GetAllBulks())

	if err != nil {
		return &bulks, err
	}
	defer TryClose(rows)

	for rows.Next() {
		record, err := scanBulkRecord(rows)
		if err != nil {
			return &bulks, err
		}

		if record != nil {
			bulks[record.ID] = *record
		}
	}
	return &bulks, nil
}

type Closeable interface {
	Close() error
}

func TryClose(closeable Closeable) {
	if closeable != nil {
		e := closeable.Close()
		if e != nil {
			fmt.Println(e)
		}
	}
}
