package api

import (
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"strconv"
	"strings"
	"time"

	"github.com/araddon/dateparse"
	"github.com/juju/errors"
	"github.com/ngaut/log"
	"github.com/pingcap/schrodinger/config"
	"github.com/pingcap/schrodinger/pkg/types"
	"github.com/pingcap/schrodinger/util"
)

const (
	defaultStartTime = "1970-01-01 00:00:00"
	defaultEndTime   = "2970-01-01 00:00:00"
)

// ListData defines the struct of list data for api
type ListData struct {
	List   interface{} `json:"list"`
	Offset int64       `json:"offset"`
	Size   int64       `json:"size"`
	Total  int64       `json:"total"`
}

func readJSON(r io.ReadCloser, data interface{}) error {
	defer r.Close()

	b, err := ioutil.ReadAll(r)
	if err == nil {
		err = json.Unmarshal(b, data)
	}

	return errors.Trace(err)
}

func filterExperimentByTime(all types.ExperimentProcesses, startTime, endTime string) (types.ExperimentProcesses, error) {
	var experiments types.ExperimentProcesses

	sTime, err := formatTime(startTime, defaultStartTime)
	if err != nil {
		return nil, errors.Trace(err)
	}

	eTime, err := formatTime(endTime, defaultEndTime)
	if err != nil {
		return nil, errors.Trace(err)
	}

	for _, experiment := range all {
		if experiment.StartTime == "" {
			continue
		}

		expStartTime, err := dateparse.ParseLocal(experiment.StartTime)
		if err != nil {
			log.Errorf("parse experiment %s start time %s failed: %v", experiment.Name, experiment.StartTime, err)
			continue
		}

		if expStartTime.After(sTime) && expStartTime.Before(eTime) {
			experiments = append(experiments, experiment)
			continue
		}

		if experiment.EndTime == "" {
			if expStartTime.Before(sTime) {
				experiments = append(experiments, experiment)
			}
			continue
		}

		expEndTime, err := dateparse.ParseLocal(experiment.EndTime)
		if err != nil {
			log.Errorf("parse experiment %s end time %s failed: %v", experiment.Name, experiment.EndTime, err)
			continue
		}

		if expEndTime.After(sTime) && expEndTime.Before(eTime) {
			experiments = append(experiments, experiment)
		}
	}

	return experiments, nil
}

func formatTime(timeStr string, defaultTime string) (time.Time, error) {
	if timeStr == "" {
		timeStr = defaultTime
	}

	return dateparse.ParseLocal(timeStr)
}

func filterConfigTemplate(
	all types.TiDBClusterConfigTemplates,
	tag []string,
	names []string,
) types.TiDBClusterConfigTemplates {
	var tpls types.TiDBClusterConfigTemplates

	for _, tpl := range all {
		if util.MatchInArray2(tag, string(tpl.Tag)) {
			if len(names) == 0 {
				tpls = append(tpls, tpl)
				continue
			}

			if strings.Contains(tpl.Name, names[0]) {
				tpls = append(tpls, tpl)
			}
		}
	}

	return tpls
}

func filterTestTemplate(
	all types.TestTemplates,
	status []string,
	typs []string,
	tags []string,
	names []string,
) types.TestTemplates {
	var tpls types.TestTemplates
	filterName := func(tpl *types.TestTemplate) {
		if len(names) == 0 {
			tpls = append(tpls, tpl)
			return
		}

		if strings.Contains(tpl.Name, names[0]) {
			tpls = append(tpls, tpl)
		}
	}

	for _, tpl := range all {
		if util.MatchInArray2(status, string(tpl.Status)) &&
			util.MatchInArray2(typs, string(tpl.Type)) {
			if len(tags) == 0 {
				filterName(tpl)
				continue
			}

			inArray := true
			for _, tag := range tags {
				if !util.MatchInArray(tpl.Tags, tag) {
					inArray = false
					break
				}
			}
			if inArray {
				filterName(tpl)
			}
		}
	}

	return tpls
}

func mergeBox(originBox *types.Box, newBox *types.Box) *types.Box {
	targetBox := originBox.Clone()
	targetBox.Name = newBox.Name
	targetBox.Utime = time.Now().Format(util.TimeFormat)
	targetBox.Rules = newBox.Rules
	targetBox.Tests = newBox.Tests
	targetBox.Misc = newBox.Misc
	targetBox.Desc = newBox.Desc
	targetBox.Creator = newBox.Creator
	targetBox.WithoutCat = newBox.WithoutCat
	if !newBox.WithoutCat {
		targetBox.Cat = newBox.Cat
	}
	if len(targetBox.State) == 0 {
		targetBox.State = types.BoxStateOnline
	}

	targetBox.SetDefaultValue()

	return targetBox
}

func getSize(r *http.Request) int64 {
	return getKeyWithDefaultValue("size", config.GetGlobalConfig().Util.PageSize, r)
}

func getOffset(r *http.Request) int64 {
	return getKeyWithDefaultValue("offset", 0, r)
}

func getLabelFilter(r *http.Request) string {
	var labelKey string
	labelKey = getKey("filter", r)
	if util.IsEmpty(labelKey) {
		return ""
	}
	labelValue := getKey(labelKey, r)
	if util.IsEmpty(labelValue) {
		return ""
	}

	return fmt.Sprintf("%s=%s", labelKey, labelValue)
}

func getKeyWithDefaultValue(key string, defaultValue int64, r *http.Request) int64 {
	var valueKey string
	valueKey = getKey(key, r)
	if util.IsEmpty(valueKey) {
		return defaultValue
	}

	value, err := strconv.ParseInt(valueKey, 10, 64)
	if err != nil {
		log.Warningf("convert value %s to int64 failed, using the default value %d ", valueKey, defaultValue)
		return defaultValue
	}

	return value
}

func getKey(key string, r *http.Request) string {
	return r.URL.Query().Get(key)
}

func getPagingIndexes(length, offset, size int64) (int64, int64) {
	firstIndex := offset
	lastIndex := offset + size
	if length <= offset {
		return 0, 0
	}

	if length < offset+size {
		lastIndex = length
	}
	return firstIndex, lastIndex
}

func toLabel(catProcesses []*types.CatProcess) []types.Label {
	var labels []types.Label
	for _, catProcess := range catProcesses {
		labels = append(labels, catProcess)
	}
	return labels
}

func filterStage(catProcesses []*types.CatProcess, stage string) []*types.CatProcess {
	var cats []*types.CatProcess
	for _, cat := range catProcesses {
		if strings.ToLower(string(cat.Stage)) == strings.ToLower(stage) {
			cats = append(cats, cat)
		}
	}
	return cats
}

func experimentsToBrief(experiments types.ExperimentProcesses) []*types.ExperimentBrief {
	var briefs []*types.ExperimentBrief
	for _, experiment := range experiments {
		briefs = append(briefs, experiment.ToBrief())
	}
	return briefs
}

func experimentsToExperimentsByTime(experiments types.ExperimentProcesses) []*ExperimentSummary {
	var summarys []*ExperimentSummary
	for _, experiment := range experiments {
		summarys = append(summarys, experimentToExperimentSummary(experiment))
	}
	return summarys
}

// experimentToExperimentSummary transfer experiment process to by time
func experimentToExperimentSummary(experiment *types.ExperimentProcess) *ExperimentSummary {
	summary := &ExperimentSummary{
		ID:   experiment.ID,
		Name: experiment.Name,

		Ctime:     experiment.Ctime,
		Utime:     experiment.Utime,
		StartTime: experiment.StartTime,
		EndTime:   experiment.EndTime,

		Stage:     experiment.Stage,
		State:     experiment.State,
		Destroyed: experiment.Destroyed,

		Author:     experiment.Author,
		Annotation: experiment.Annotation,
	}
	return summary
}

func getLatestBackup(backups []*types.TiDBClusterBackup) (*types.TiDBClusterBackup, error) {
	if len(backups) == 0 {
		return nil, errors.New("backups is empty")
	}
	latest := backups[0]
	for _, backup := range backups {
		if backup.Ctime > latest.Ctime {
			latest = backup
		}
	}
	return latest, nil
}
