package api

import (
	"net/http"
	"strconv"

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

// ListCat lists cat
func (h *ManagerHandler) ListCat(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"]

	cats, total := h.mgr.ListCat(&storage.CatProcessConditions{
		Names:     names,
		Stage:     stage,
		Status:    status,
		Destroyed: destroyed,
		Limit:     size,
		Offset:    offset,
	})

	listData := ListData{
		List:   cats,
		Offset: offset,
		Size:   size,
		Total:  total,
	}
	h.rdr.JSON(w, http.StatusOK, util.SuccessResponse(listData))
}

// ListCatNames lists cat
func (h *ManagerHandler) ListCatNames(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"]

	cats, _ := h.mgr.ListCat(&storage.CatProcessConditions{
		Stage:     stage,
		Status:    status,
		Destroyed: destroyed,
		Names:     names,
		Limit:     size,
		Offset:    offset,
	})
	names = cats.Names()

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

// GetCat gets cat
func (h *ManagerHandler) GetCat(w http.ResponseWriter, r *http.Request) {
	catID, err := strconv.ParseInt(mux.Vars(r)["id"], 10, 64)
	if err != nil {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("get cat id error %v", err))
		return
	}
	cat, exist := h.mgr.GetCat(catID)
	if !exist {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("cat %d not found in storage", catID))
		return
	}
	h.rdr.JSON(w, http.StatusOK, util.SuccessResponse(cat))
}

// GetCatByName gets cat
func (h *ManagerHandler) GetCatByName(w http.ResponseWriter, r *http.Request) {
	catName := mux.Vars(r)["name"]
	if util.IsEmpty(catName) {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("cat %s not found in storage", catName))
		return
	}
	cat, exist := h.mgr.GetCatByName(catName)
	if !exist {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("cat %s not found in storage", catName))
		return
	}
	h.rdr.JSON(w, http.StatusOK, util.SuccessResponse(cat))
}

// GetCatDetail gets cat detail
func (h *ManagerHandler) GetCatDetail(w http.ResponseWriter, r *http.Request) {
	catID, err := strconv.ParseInt(mux.Vars(r)["id"], 10, 64)
	if err != nil {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("get cat id error %v", err))
		return
	}
	cat, exist := h.mgr.GetCat(catID)
	if !exist {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("cat %d not found in storage", catID))
		return
	}
	h.rdr.JSON(w, http.StatusOK, util.SuccessResponse(cat.Report))
}

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

	var config = new(types.Cat)

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

	cat, exist := h.mgr.GetCat(catID)
	if !exist {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("cat %d is not exist", catID))
		return
	}
	if !cat.IsRunning() {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("cat stage %s is not running", cat.Stage))
		return
	}

	waitOk := false
	waitOkStr := getKey("waitok", r)
	if waitOkStr == "true" {
		waitOk = true
	}

	config.SetDefaultConfig()

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

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

// CreateCat creates cats
func (h *ManagerHandler) CreateCat(w http.ResponseWriter, r *http.Request) {
	var config = new(types.Cat)

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

	if !config.IsNameExist() {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("when create cat alone, should provide name"))
		return
	}

	if err := config.Verify(); err != nil {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("verify cat failed %v", err))
		return
	}

	waitOk := false
	waitOkStr := getKey("waitok", r)
	if waitOkStr == "true" {
		waitOk = true
	}

	config.SetDefaultConfig()

	cat, err := h.mgr.CreateCat(config, waitOk)
	if err != nil {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("create cat %s error %v", config.Name, err))
		return
	}

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

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

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

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

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

	_, exist := h.mgr.GetCat(catID)
	if !exist {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("cat %d is not exist", catID))
		return
	}

	backupName := mux.Vars(r)["backupName"]
	if util.IsEmpty(backupName) {
		backupName = util.GenRandomBackupName()
		log.Infof("backup cat %d, using random backupName %s", catID, backupName)
	}

	if err = h.mgr.BackupCat(catID, backupName); err != nil {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("backup cat %d error %v", catID, err))
		return
	}

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

// RestoreCat restores cat with the physical backup
func (h *ManagerHandler) RestoreCat(w http.ResponseWriter, r *http.Request) {
	catID, err := strconv.ParseInt(mux.Vars(r)["id"], 10, 64)
	if err != nil {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("get cat id error %v", err))
		return
	}
	_, exist := h.mgr.GetCat(catID)
	if !exist {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("cat %d is not exist", catID))
		return
	}

	backupName := mux.Vars(r)["backupName"]
	if util.IsEmpty(backupName) {
		var backups []*types.TiDBClusterBackup
		backups, err = h.mgr.ListCatBackups(catID)
		if err != nil {
			h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("fail to restore cat %d, error: %v", catID, err))
			return
		}
		if len(backups) == 0 {
			h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("fail to restore cat %d, no backups in this cat", catID))
			return
		}
		latestBackup, _ := getLatestBackup(backups)

		log.Infof("restore cat %d, use backup %s", catID, latestBackup.Name)
		backupName = latestBackup.Name
	}

	if err = h.mgr.RestoreCat(catID, backupName); err != nil {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("restore cat %d error %v", catID, err))
		return
	}

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

// GetCatStatus returns the stage and state of the cat
func (h *ManagerHandler) GetCatStatus(w http.ResponseWriter, r *http.Request) {
	catID, err := strconv.ParseInt(mux.Vars(r)["id"], 10, 64)
	if err != nil {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("get cat id error %v", err))
		return
	}

	status, err := h.mgr.GetCatStatus(catID)
	if err != nil {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("get cat %d stage error %v", catID, err))
		return
	}

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

// ListCatBackups returns the all backups of the cat
func (h *ManagerHandler) ListCatBackups(w http.ResponseWriter, r *http.Request) {
	catID, err := strconv.ParseInt(mux.Vars(r)["id"], 10, 64)
	if err != nil {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("get cat id error %v", err))
		return
	}

	backups, err := h.mgr.ListCatBackups(catID)
	if err != nil {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("fail to list backups of the cat %d, error: %v", catID, err))
		return
	}

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

// ListCatConfigTemplates lists all the cat config templates
func (h *ManagerHandler) ListCatConfigTemplates(w http.ResponseWriter, r *http.Request) {
	names := r.URL.Query()["name"]
	tag := r.URL.Query()["tag"]
	size := getSize(r)
	offset := getOffset(r)

	all, err := h.mgr.ListCatConfigTemplates()
	if err != nil {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("list config templates failed: %v", err))
		return
	}

	tpls := filterConfigTemplate(all, tag, names)

	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))
}

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

	tpl, err := h.mgr.GetCatConfigTemplate(tplID)
	if err != nil {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("fail to get config template by id %d, error: %v", tplID, err))
		return
	}

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

// GetCatConfigTemplateByName returns the config template of cat by id
func (h *ManagerHandler) GetCatConfigTemplateByName(w http.ResponseWriter, r *http.Request) {
	name := mux.Vars(r)["name"]
	if util.IsEmpty(name) {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponse("name can't be empty"))
		return
	}

	tpl, err := h.mgr.GetCatConfigTemplateByName(name)
	if err != nil {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("fail to get config template by name %s, error: %v", name, err))
		return
	}

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

// GetCatConfigTemplatesByTag returns all the config templates with the `tag`
func (h *ManagerHandler) GetCatConfigTemplatesByTag(w http.ResponseWriter, r *http.Request) {
	tag := mux.Vars(r)["tag"]
	if util.IsEmpty(tag) {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponse("tag of template can't be empty"))
		return
	}

	tpls, err := h.mgr.GetCatConfigTemplatesByTag(tag)
	if err != nil {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("get config templates failed: %v", err))
		return
	}

	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))
}

// CreateCatConfigTemplate creates a config template for cat
func (h *ManagerHandler) CreateCatConfigTemplate(w http.ResponseWriter, r *http.Request) {
	tpl := new(types.TiDBClusterConfigTemplate)
	if err := readJSON(r.Body, tpl); err != nil {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("read json body error %v", err))
		return
	}

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

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

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

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

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

	if err = tpl.Verify(); err != nil {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("verify template %+v error %v", tpl, err))
		return
	}
	oldTPL, err := h.mgr.GetCatConfigTemplate(tplID)
	if err != nil {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("can not get config template %d from storage", tplID))
		return
	}

	tpl.ID = tplID
	tpl.Ctime = oldTPL.Ctime

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

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

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

	if err := h.mgr.DeleteCatConfigTemplate(tplID); err != nil {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("delete config template %d failed: %v", tplID, err))
		return
	}

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

// ListCatConfigTemplateNames returns the names of the cat template by fillter
func (h *ManagerHandler) ListCatConfigTemplateNames(w http.ResponseWriter, r *http.Request) {
	tag := r.URL.Query()["tag"]
	searchNames := r.URL.Query()["name"]

	all, err := h.mgr.ListCatConfigTemplates()
	if err != nil {
		h.rdr.JSON(w, http.StatusOK, util.ErrResponsef("get config template failed: %v", err))
		return
	}

	tpls := filterConfigTemplate(all, tag, searchNames)

	names := tpls.Names()

	size := getSize(r)
	offset := getOffset(r)
	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))
}
