package api

import (
	"net/http"
	"sort"
	"strconv"

	"github.com/gorilla/mux"
	"github.com/pingcap/schrodinger/pkg/storage"
	"github.com/pingcap/schrodinger/pkg/types"
	"github.com/pingcap/schrodinger/util"
)

// CreateTestTemplate sets test template
func (h *ManagerHandler) CreateTestTemplate(w http.ResponseWriter, r *http.Request) {
	c := new(types.TestTemplate)
	if err := readJSON(r.Body, c); err != nil {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("read json body error %v", err))
		return
	}

	if err := c.Verify(); err != nil {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("verify template %+v error %v", c, err))
		return
	}

	c.ClearErrorInfo()

	c.SetDefaultResourceConfig()
	if err := h.mgr.CreateTestTemplate(c); err != nil {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("store template %+v error %v", c, err))
		return
	}

	h.rdr.JSON(w, http.StatusOK, util.SuccessResponse(c))
}

// UpdateTestTemplate updates test template
func (h *ManagerHandler) UpdateTestTemplate(w http.ResponseWriter, r *http.Request) {
	name := mux.Vars(r)["name"]
	id, err := strconv.ParseInt(mux.Vars(r)["id"], 10, 64)

	if err != nil {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("expect a number"))
		return
	}

	_, err = h.mgr.GetTestTemplateByID(id)
	if err != nil {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("get case template %s/%d error %v", name, id, err))
		return
	}

	c := new(types.TestTemplate)
	if err := readJSON(r.Body, c); err != nil {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("read json body error %v", err))
		return
	}

	if err := c.Verify(); err != nil {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("verify template %+v error %v", c, err))
		return
	}

	if err := h.mgr.UpdateTestTemplate(c); err != nil {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("store template %+v error %v", c, err))
		return
	}

	h.rdr.JSON(w, http.StatusOK, util.SuccessResponse(c))
}

// GetTestTemplateByName gets test template
func (h *ManagerHandler) GetTestTemplateByName(w http.ResponseWriter, r *http.Request) {
	name := mux.Vars(r)["name"]

	ct, err := h.mgr.GetTestTemplateByName(name)
	if err != nil {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("get case template %s error %v", name, err))
		return
	}

	h.rdr.JSON(w, http.StatusOK, util.SuccessResponse(ct))
}

// GetTestTemplateByID gets test template
func (h *ManagerHandler) GetTestTemplateByID(w http.ResponseWriter, r *http.Request) {
	name := mux.Vars(r)["name"]
	id, err := strconv.ParseInt(mux.Vars(r)["id"], 10, 64)
	if err != nil {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("get test template id error %v", err))
		return
	}

	ct, err := h.mgr.GetTestTemplateByID(id)
	if err != nil {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("get case template [name %s, id %d] error %v", name, id, err))
		return
	}

	h.rdr.JSON(w, http.StatusOK, util.SuccessResponse(ct))
}

// GetTestTemplateByNameAndLatest gets test template by latest
func (h *ManagerHandler) GetTestTemplateByNameAndLatest(w http.ResponseWriter, r *http.Request) {
	name := mux.Vars(r)["name"]

	ct, err := h.mgr.GetTestTemplateByNameAndLatest(name)
	if err != nil {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("get case template %s/latest error %v", name, err))
		return
	}

	h.rdr.JSON(w, http.StatusOK, util.SuccessResponse(ct))
}

// GetTestTemplateByNameAndPR gets test template by pr
func (h *ManagerHandler) GetTestTemplateByNameAndPR(w http.ResponseWriter, r *http.Request) {
	name := mux.Vars(r)["name"]
	id := mux.Vars(r)["id"]

	ct, err := h.mgr.GetTestTemplateByNameAndPR(name, id)
	if err != nil {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("get case template %s/pr/%s error %v", name, id, err))
		return
	}

	h.rdr.JSON(w, http.StatusOK, util.SuccessResponse(ct))
}

// GetTestTemplatesByTag gets boxes by kind
func (h *ManagerHandler) GetTestTemplatesByTag(w http.ResponseWriter, r *http.Request) {
	tag := mux.Vars(r)["tag"]
	if util.IsEmpty(tag) {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("test template tag can not be empty"))
		return
	}

	tpls := h.mgr.GetTestTemplatesByTag(tag)
	size := getSize(r)
	offset := getOffset(r)
	start, end := getPagingIndexes(int64(len(tpls)), offset, size)

	listData := ListData{
		List:   tpls[start:end],
		Offset: offset,
		Size:   size,
		Total:  int64(len(tpls)),
	}

	h.rdr.JSON(w, http.StatusOK, util.SuccessResponse(listData))
}

// DeleteTestTemplate deletes test template
func (h *ManagerHandler) DeleteTestTemplate(w http.ResponseWriter, r *http.Request) {
	name := mux.Vars(r)["name"]
	id, err := strconv.ParseInt(mux.Vars(r)["id"], 10, 64)
	if err != nil {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("get test template id error %v", err))
		return
	}
	h.mgr.DeleteTestTemplate(name, id)

	h.rdr.JSON(w, http.StatusOK, util.SuccessResponse("OK"))
}

// ListTestTemplate lists case templates
func (h *ManagerHandler) ListTestTemplate(w http.ResponseWriter, r *http.Request) {
	status := r.URL.Query()["status"]
	typs := r.URL.Query()["type"]
	tags := r.URL.Query()["tag"]
	names := r.URL.Query()["name"]
	size := getSize(r)
	offset := getOffset(r)

	all := h.mgr.ListTestTemplate()
	cts := filterTestTemplate(all, status, typs, tags, names)
	start, end := getPagingIndexes(int64(len(cts)), offset, size)

	listData := ListData{
		List:   cts[start:end],
		Offset: offset,
		Size:   size,
		Total:  int64(len(cts)),
	}
	h.rdr.JSON(w, http.StatusOK, util.SuccessResponse(listData))
}

// ListTestTemplateNames lists the name of the templates
func (h *ManagerHandler) ListTestTemplateNames(w http.ResponseWriter, r *http.Request) {
	status := r.URL.Query()["status"]
	typs := r.URL.Query()["type"]
	tags := r.URL.Query()["tag"]
	searchNames := r.URL.Query()["tag"]
	size := getSize(r)
	offset := getOffset(r)

	all := h.mgr.ListTestTemplate()
	cts := filterTestTemplate(all, status, typs, tags, searchNames)
	names := cts.Names()

	start, end := getPagingIndexes(int64(len(names)), offset, size)
	listData := ListData{
		List:   names[start:end],
		Offset: offset,
		Size:   size,
		Total:  int64(len(names)),
	}
	h.rdr.JSON(w, http.StatusOK, util.SuccessResponse(listData))
}

// ListTestTemplateNameWithTags lists the name of the templates
func (h *ManagerHandler) ListTestTemplateNameWithTags(w http.ResponseWriter, r *http.Request) {
	status := r.URL.Query()["status"]
	typs := r.URL.Query()["type"]
	tags := r.URL.Query()["tag"]
	searchNames := r.URL.Query()["tag"]
	size := getSize(r)
	offset := getOffset(r)

	all := h.mgr.ListTestTemplate()
	cts := filterTestTemplate(all, status, typs, tags, searchNames)
	names := cts.NameWithTags()

	start, end := getPagingIndexes(int64(len(names)), offset, size)
	listData := ListData{
		List:   names[start:end],
		Offset: offset,
		Size:   size,
		Total:  int64(len(names)),
	}
	h.rdr.JSON(w, http.StatusOK, util.SuccessResponse(listData))
}

// GetTest gets tests
func (h *ManagerHandler) GetTest(w http.ResponseWriter, r *http.Request) {
	experimentID, err := strconv.ParseInt(mux.Vars(r)["experimentID"], 10, 64)
	if err != nil {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("get experiment id error %v", err))
		return
	}

	testName, exist := mux.Vars(r)["name"]
	if !exist {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("experiment %d do not pass testName", experimentID))
		return
	}

	experiment, exist := h.mgr.GetExperimentProcess(experimentID)
	if !exist {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("experiment %d not exist ", experimentID))
		return
	}

	test, exist := experiment.Tests[testName]
	if !exist {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("experiment %d test %s not exist ", experimentID, testName))
		return
	}
	h.rdr.JSON(w, http.StatusOK, util.SuccessResponse(test))
}

// StartTest starts test in experiment
func (h *ManagerHandler) StartTest(w http.ResponseWriter, r *http.Request) {
	experimentID, err := strconv.ParseInt(mux.Vars(r)["experimentID"], 10, 64)
	if err != nil {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("get experiment id error %v", err))
		return
	}

	testName := mux.Vars(r)["name"]
	err = h.mgr.StartTest(experimentID, testName)
	if err != nil {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("start test %s error %v", testName, err))
		return
	}
	h.rdr.JSON(w, http.StatusOK, util.SuccessResponse("ok"))
}

// StopTest stops test in experiment
func (h *ManagerHandler) StopTest(w http.ResponseWriter, r *http.Request) {
	experimentID, err := strconv.ParseInt(mux.Vars(r)["experimentID"], 10, 64)
	if err != nil {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("get experiment id error %v", err))
		return
	}

	testName := mux.Vars(r)["name"]
	err = h.mgr.StopTest(experimentID, testName)
	if err != nil {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("stop test %s error %v", testName, err))
		return
	}
	h.rdr.JSON(w, http.StatusOK, util.SuccessResponse("ok"))
}

// RemoveTest removes test in experiment
func (h *ManagerHandler) RemoveTest(w http.ResponseWriter, r *http.Request) {
	experimentID, err := strconv.ParseInt(mux.Vars(r)["experimentID"], 10, 64)
	if err != nil {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("get experiment id error %v", err))
		return
	}

	testName := mux.Vars(r)["name"]

	err = h.mgr.RemoveTest(experimentID, testName)
	if err != nil {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("stop test %s error %v", testName, err))
		return
	}
	h.rdr.JSON(w, http.StatusOK, util.SuccessResponse("ok"))
}

// ListTest lists tests
func (h *ManagerHandler) ListTest(w http.ResponseWriter, r *http.Request) {
	experimentID, err := strconv.ParseInt(mux.Vars(r)["experimentID"], 10, 64)
	if err != nil {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("get experiment id error %v", err))
		return
	}

	experiment, exist := h.mgr.GetExperimentProcess(experimentID)
	if !exist {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("experiment %d not found", experimentID))
		return
	}

	var tests []*types.TestProcess
	for _, test := range experiment.Tests {
		tests = append(tests, test)
	}

	size := getSize(r)
	offset := getOffset(r)
	start, end := getPagingIndexes(int64(len(tests)), offset, size)

	listData := ListData{
		List:   tests[start:end],
		Offset: offset,
		Size:   size,
		Total:  int64(len(tests)),
	}

	h.rdr.JSON(w, http.StatusOK, util.SuccessResponse(listData))
}

// GetTestDetail gets test detail
func (h *ManagerHandler) GetTestDetail(w http.ResponseWriter, r *http.Request) {
	experimentID, err := strconv.ParseInt(mux.Vars(r)["experimentID"], 10, 64)
	if err != nil {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("get experiment id error %v", err))
		return
	}

	testName, exist := mux.Vars(r)["name"]
	if !exist {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("experiment %d do not pass testName", experimentID))
		return
	}

	experiment, exist := h.mgr.GetExperimentProcess(experimentID)
	if !exist {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("experiment %d not found", experimentID))
		return
	}
	for _, test := range experiment.Report.Tests {
		if test.Name == testName {
			h.rdr.JSON(w, http.StatusOK, util.SuccessResponse(test))
			return
		}
	}

	h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("experiment %d test %s not exist ", experimentID, testName))
}

// GetTestConfig returns specified test config by test ID
func (h *ManagerHandler) GetTestConfig(w http.ResponseWriter, r *http.Request) {
	id, err := strconv.ParseInt(mux.Vars(r)["id"], 10, 64)
	if err != nil {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("get test id error %v", err))
		return
	}

	config, err := h.mgr.GetTestConfig(id)
	if err != nil {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("get test %d config error %v", id, err))
		return
	}

	h.rdr.JSON(w, http.StatusOK, util.SuccessResponse(config))
}

// UploadTest uploads reasult by test
// TODO: implement it
func (h *ManagerHandler) UploadTest(w http.ResponseWriter, r *http.Request) {
	h.rdr.JSON(w, http.StatusOK, util.SuccessResponse("ok"))
}

// SetTestResult sets test result
func (h *ManagerHandler) SetTestResult(w http.ResponseWriter, r *http.Request) {
	tr := new(types.TestResult)
	if err := readJSON(r.Body, tr); err != nil {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("read json body error %v", err))
		return
	}

	if err := tr.Verify(); err != nil {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("verify test result %v error %v", tr, err))
		return
	}

	if err := h.mgr.SetTestResult(tr); err != nil {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("store test result %v, error %v", tr, err))
		return
	}

	h.rdr.JSON(w, http.StatusOK, util.SuccessResponse(tr))
}

// GetTestResult returns specified test result by result ID
func (h *ManagerHandler) GetTestResult(w http.ResponseWriter, r *http.Request) {
	id, err := strconv.ParseInt(mux.Vars(r)["id"], 10, 64)
	if err != nil {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("get test result id error %v", err))
		return
	}

	result, err := h.mgr.GetTestResult(id)
	if err != nil {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("get test %d result error %v", id, err))
		return
	}

	h.rdr.JSON(w, http.StatusOK, util.SuccessResponse(result))
}

// GetTestResultByExperimentWithTestName returns specified test result by the name of test and experiment ID
func (h *ManagerHandler) GetTestResultByExperimentWithTestName(w http.ResponseWriter, r *http.Request) {
	experimentID, err := strconv.ParseInt(mux.Vars(r)["experimentID"], 10, 64)
	if err != nil {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("get experiment id error %v", err))
		return
	}

	testName, exist := mux.Vars(r)["testName"]
	if !exist {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponse("the name of test is required"))
		return
	}

	result, err := h.mgr.GetTestResultByExperimentWithTestName(experimentID, testName)
	if err != nil {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("get test result by experiment id %d and test name %s error %v", experimentID, testName, err))
		return
	}

	h.rdr.JSON(w, http.StatusOK, util.SuccessResponse(result))
}

// ListTestResultsByExperiment returns all test results by the experiment ID
func (h *ManagerHandler) ListTestResultsByExperiment(w http.ResponseWriter, r *http.Request) {
	experimentID, err := strconv.ParseInt(mux.Vars(r)["experimentID"], 10, 64)
	if err != nil {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("get experiment id error %v", err))
		return
	}

	size := getSize(r)
	offset := getOffset(r)

	results := h.mgr.ListTestResultsByExperiment(experimentID)
	start, end := getPagingIndexes(int64(len(results)), offset, size)

	listData := ListData{
		List:   results[start:end],
		Offset: offset,
		Size:   size,
		Total:  int64(len(results)),
	}
	h.rdr.JSON(w, http.StatusOK, util.SuccessResponse(listData))
}

// ListTestResultsByBox returns all test results by the box ID
func (h *ManagerHandler) ListTestResultsByBox(w http.ResponseWriter, r *http.Request) {
	boxID, err := strconv.ParseInt(mux.Vars(r)["boxID"], 10, 64)
	if err != nil {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("get box id error %v", err))
		return
	}

	startTime := r.URL.Query().Get("startTime")
	endTime := r.URL.Query().Get("endTime")

	experiments, _ := h.mgr.ListExperimentProcess(&storage.ExperimentProcessConditions{
		Box: []int64{boxID},
	})
	experiments, err = filterExperimentByTime(experiments, startTime, endTime)

	if err != nil {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("list experiments by startime %s, endtime %s failed: %v", startTime, endTime, err))
		return
	}

	sort.SliceStable(experiments, func(i, j int) bool { return experiments[i].StartTime < experiments[j].StartTime })

	allResults := make(map[string][]*types.TestResult)
	for _, exp := range experiments {
		expResults := h.mgr.ListTestResultsByExperiment(exp.ID)
		for _, result := range expResults {
			allResults[result.TestName] = append(allResults[result.TestName], result)
		}
	}

	listData := ListData{
		List:   allResults,
		Offset: 0,
		Size:   int64(len(allResults)),
		Total:  int64(len(allResults)),
	}
	h.rdr.JSON(w, http.StatusOK, util.SuccessResponse(listData))
}
