package storage

import (
	"fmt"
	"github.com/go-sql-driver/mysql"
	"github.com/jmoiron/sqlx"
	"time"

	"github.com/juju/errors"
	"github.com/pingcap/schrodinger/pkg/types"
	"github.com/pingcap/schrodinger/util"
)

func (m *mysqlClient) createBuildTask(task *types.BuildTask) error {
	t := time.Now().Format(util.TimeFormat)
	task.Ctime, task.Utime = t, t

	result, err := m.db.NamedExec(stmtBuildTaskInsert, buildTaskToParams(task))
	if err != nil {
		return errors.Trace(err)
	}

	task.ID, err = result.LastInsertId()
	if err != nil {
		return errors.Trace(err)
	}

	return nil
}

func buildTaskToParams(task *types.BuildTask) map[string]interface{} {
	return map[string]interface{}{
		"id":             task.ID,
		"status":         task.Status,
		"create_time":    getDatetime(task.Ctime),
		"update_time":    getDatetime(task.Utime),
		"finish_time":    getDatetime(task.Ftime),
		"repo":           task.Information.Repo,
		"branch":         task.Information.Branch,
		"platform":       task.Information.Platform,
		"compile_option": task.Information.CompileOption,
		"build_time":     getDatetime(task.Information.BuildTime),
		"git_hash":       task.Information.Githash,
		"binary_url":     task.Information.BinaryURL,
		"git_tag":        task.Information.Tag,
		"case_name":      task.Information.Case,
		"force_t":        task.Information.Force,
		"image":          task.Information.Image,
		"build_log":      task.Information.BuildLog,
		"fuse":           task.Information.Fuse,
	}
}

const stmtBuildTaskInsert = `
INSERT INTO build_task (
 status
,create_time
,update_time
,finish_time
,repo
,branch
,platform
,compile_option
,build_time
,git_hash
,binary_url
,git_tag
,case_name
,force_t
,image
,build_log
,fuse
) VALUES (
 :status
,:create_time
,:update_time
,:finish_time
,:repo
,:branch
,:platform
,:compile_option
,:build_time
,:git_hash
,:binary_url
,:git_tag
,:case_name
,:force_t
,:image
,:build_log
,:fuse
)
`

func (m *mysqlClient) updateBuildTask(task *types.BuildTask) error {
	task.Utime = time.Now().Format(util.TimeFormat)

	if _, err := m.db.NamedExec(stmtBuildTaskUpdate, buildTaskToParams(task)); err != nil {
		return errors.Trace(err)
	}

	return nil
}

const stmtBuildTaskUpdate = `
UPDATE build_task SET
 status = :status
,create_time = :create_time
,update_time = :update_time
,finish_time = :finish_time
,repo = :repo
,branch = :branch
,platform = :platform
,compile_option = :compile_option
,build_time = :build_time
,git_hash = :git_hash
,binary_url = :binary_url
,git_tag = :git_tag
,case_name = :case_name
,force_t = :force_t
,image = :image
,build_log = :build_log
,fuse = :fuse
WHERE id = :id
`

func (m *mysqlClient) findBuildTaskByID(id int64) (*types.BuildTask, error) {
	query, args, err := m.db.BindNamed(stmtBuildTaskQueryByID, map[string]interface{}{"id": id})
	if err != nil {
		return nil, errors.Trace(err)
	}

	row := m.db.QueryRowx(query, args...)

	task := &types.BuildTask{}

	if err := scanBuildTaskRow(row, task); err != nil {
		return nil, errors.Trace(err)
	}

	return task, nil
}

func scanBuildTaskRow(scanner Scanner, dest *types.BuildTask) error {
	finishTime := mysql.NullTime{}
	buildTime := mysql.NullTime{}

	err := scanner.Scan(
		&dest.ID,
		&dest.Status,
		&dest.Ctime,
		&dest.Utime,
		&finishTime,
		&dest.Information.Repo,
		&dest.Information.Branch,
		&dest.Information.Platform,
		&dest.Information.CompileOption,
		&buildTime,
		&dest.Information.Githash,
		&dest.Information.BinaryURL,
		&dest.Information.Tag,
		&dest.Information.Case,
		&dest.Information.Force,
		&dest.Information.Image,
		&dest.Information.BuildLog,
		&dest.Information.Fuse,
	)

	if finishTime.Valid {
		dest.Ftime = finishTime.Time.Format(util.TimeFormat)
	}

	if buildTime.Valid {
		dest.Information.BuildTime = buildTime.Time.Format(util.TimeFormat)
	}

	return errors.Trace(err)
}

func scanBuildTaskRows(rows *sqlx.Rows) ([]*types.BuildTask, error) {
	defer rows.Close()

	var tasks []*types.BuildTask

	for rows.Next() {
		task := &types.BuildTask{}

		if err := scanBuildTaskRow(rows, task); err != nil {
			return nil, errors.Trace(err)
		}

		tasks = append(tasks, task)
	}

	return tasks, nil
}

const stmtBuildTaskQueryBase = `
SELECT 
 id
,status
,create_time
,update_time
,finish_time
,repo
,branch
,platform
,compile_option
,build_time
,git_hash
,binary_url
,git_tag
,case_name
,force_t
,image
,build_log
,fuse
FROM build_task
`

const stmtBuildTaskQueryByID = stmtBuildTaskQueryBase + `
WHERE id = :id
`

// BuildTaskConditions conducts the storage query
type BuildTaskConditions struct {
	Status []types.TaskStatus
	Limit  int64
	Offset int64
}

func (b *BuildTaskConditions) genStmtSQL() string {
	sql := stmtBuildTaskQueryBase + `WHERE 1 = 1`

	if len(b.Status) > 0 {
		var status []string
		for _, s := range b.Status {
			status = append(status, string(s))
		}
		sql += genInClause("status", status)
	}

	sql += ` ORDER BY create_time DESC`

	if b.Limit != 0 {
		sql += fmt.Sprintf(" LIMIT %d OFFSET %d", b.Limit, b.Offset)
	}

	return sql
}

func (m *mysqlClient) listBuildTask(condition BuildTaskConditions) ([]*types.BuildTask, error) {
	rows, err := m.db.Queryx(condition.genStmtSQL())
	if err != nil {
		return nil, errors.Trace(err)
	}

	tasks, err := scanBuildTaskRows(rows)
	if err != nil {
		return nil, errors.Trace(err)
	}

	return tasks, nil
}
