package api

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

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

// Experiment has Stop,Restart,List operation

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

	err = h.mgr.DestroyExperiment(experimentID)
	if err != nil {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("stop experiment %d error %v", experimentID, err))
		return
	}

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

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

	annotation := &struct {
		Annotation string `json:"annotation"`
		Author     string `json:"author"`
	}{}

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

	err = h.mgr.AnnotateExperiment(experimentID, annotation.Annotation, annotation.Author)
	if err != nil {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("mark experiment %d error %v", experimentID, err))
		return
	}

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

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

	err = h.mgr.RedoExperiment(experimentID)
	if err != nil {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("restart experiment %d error %v", experimentID, err))
		return
	}

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

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

	err = h.mgr.CancelExperiment(experimentID)
	if err != nil {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("cancel experiment %d error %v", experimentID, err))
		return
	}

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

// ListExperimentByBoxID lists experiment by box id
func (h *ManagerHandler) ListExperimentByBoxID(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
	}

	size := getSize(r)
	offset := getOffset(r)
	stage := r.URL.Query()["stage"]
	status := r.URL.Query()["status"]
	destroyed := r.URL.Query()["destroyed"]
	names := r.URL.Query()["name"]

	experiments, total := h.mgr.ListExperimentProcess(&storage.ExperimentProcessConditions{
		Box:       []int64{boxID},
		Stage:     stage,
		Status:    status,
		Destroyed: destroyed,
		Names:     names,
		Limit:     size,
		Offset:    offset,
	})
	listData := ListData{
		List:   experimentsToBrief(experiments),
		Offset: offset,
		Size:   size,
		Total:  total,
	}
	h.rdr.JSON(w, http.StatusOK, util.SuccessResponse(listData))
}

// ListExperiment lists experiment
func (h *ManagerHandler) ListExperiment(w http.ResponseWriter, r *http.Request) {
	size := getSize(r)
	offset := getOffset(r)
	stage := r.URL.Query()["stage"]
	status := r.URL.Query()["status"]
	destroyed := r.URL.Query()["destroyed"]
	names := r.URL.Query()["name"]

	experiments, total := h.mgr.ListExperimentProcess(&storage.ExperimentProcessConditions{
		Stage:     stage,
		Status:    status,
		Destroyed: destroyed,
		Names:     names,
		Limit:     size,
		Offset:    offset,
	})
	listData := ListData{
		List:   experimentsToBrief(experiments),
		Offset: offset,
		Size:   size,
		Total:  total,
	}
	h.rdr.JSON(w, http.StatusOK, util.SuccessResponse(listData))
}

// GetExperiment gets experiment
func (h *ManagerHandler) GetExperiment(w http.ResponseWriter, r *http.Request) {
	experimentID, err := strconv.ParseInt(mux.Vars(r)["id"], 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
	}
	h.rdr.JSON(w, http.StatusOK, util.SuccessResponse(experiment))
}

// GetExperimentDetail gets experiment detail
func (h *ManagerHandler) GetExperimentDetail(w http.ResponseWriter, r *http.Request) {
	experimentID, err := strconv.ParseInt(mux.Vars(r)["id"], 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
	}

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

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

	report, err := h.mgr.GetExperimentReport(experimentID, template.DetailReportTemplateName, template.GenDetailReportTemplate())
	if err != nil {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("get experiment %d detail report %v error: %v", experimentID, report, err))
		return
	}

	h.rdr.Text(w, http.StatusOK, report)
}

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

	var config = new(types.Cat)

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

	err = h.mgr.ReplaceCat(experimentID, config)
	if err != nil {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("replace cat %d error %v", experimentID, err))
		return
	}

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

// ListExperimentByTime lists all experiments by startTime and endTime.
func (h *ManagerHandler) ListExperimentByTime(w http.ResponseWriter, r *http.Request) {
	startTime := r.URL.Query().Get("starttime")
	endTime := r.URL.Query().Get("endtime")

	all, _ := h.mgr.ListExperimentProcess(nil)

	experiments, err := filterExperimentByTime(all, strings.TrimSpace(startTime), strings.TrimSpace(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
	}

	exps := experimentsToExperimentsByTime(experiments)

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

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