package handler

import (
	"net/http"
	"strconv"
	"time"

	"github.com/labstack/echo/v4"

	"github.com/pingcap/test-infra/services/tcms/service/common"

	"github.com/pingcap/test-infra/common/model"
	respM "github.com/pingcap/test-infra/common/model/tcms/response"
	serror "github.com/pingcap/test-infra/services/tcms/service/common/errors"
	"github.com/pingcap/test-infra/services/tcms/utils"
)

var _ respM.TestBedInfoListResponse
var _ model.TestBedInfo

// ListTestBedInfos ...
// @Summary List testbeds
// @ID list-testbed
// @Tags testbed
// @Param names query []string false "Filter by testbed names" collectionFormat(csv) maxItems(100) uniqueItems(true)
// @Param plan_exec_id query uint64 false "Filter by plan-execution id"
// @Param show_released query bool false "also show released testbeds"
// @Param offset query integer false "Offset/skip number of testbeds" default(0) minimum(0)
// @Param count query integer false "Number of testbeds returned" default(20) minimum(1) maximum(100)
// @Success 200 {object} respM.TestBedInfoListResponse
// @Failure default {object} HTTPError
// @Router /testbeds/info [get]
func (h *HTTPHandler) ListTestBedInfos(c echo.Context) error {
	ctx := c.(utils.Context)
	log := ctx.Log()

	opts := common.TestbedQueryOpts{}

	names, err := utils.ParseCSV(ctx.QueryParam("names"))
	if err != nil {
		return serror.New(http.StatusBadRequest, err)
	}
	opts.Names = names

	showReleased, _ := strconv.ParseBool(ctx.QueryParam("show_released"))

	id, err := strconv.Atoi(ctx.QueryParam("plan_exec_id"))
	if err == nil {
		planExecID := model.ID(id)
		log.With("planExecutionID", planExecID).Infof("ListTestbeds")
		opts.PlanExecID = &planExecID

		// list by plan-execution will return all testbeds
		showReleased = true
	}

	if !showReleased {
		opts.Status = []model.TestBedInfoStatus{
			model.TestBedInfoStatusInitiating,
			model.TestBedInfoStatusReady,
		}
	}

	offset, err := strconv.Atoi(c.QueryParam("offset"))
	if offset < 0 || err != nil {
		offset = 0
	}

	count, err := strconv.Atoi(c.QueryParam("count"))
	if count <= 0 || err != nil {
		count = 20
	} else if count > 500 {
		count = 500
	}
	opts.Offset = uint(offset)
	opts.Count = uint(count)

	testbeds, total, err := h.service.Testbed.List(ctx, opts)
	if err != nil {
		return err
	}

	return ctx.JSON(http.StatusOK, struct {
		Total int         `json:"total"`
		Data  interface{} `json:"data"`
	}{
		Total: total,
		Data:  testbeds,
	})
}

// CreateTestBedInfo ...
// @Summary Create new testbed
// @ID create-testbed
// @Tags testbed
// @Security Bearer
// @Param testbed body model.TestBedInfo true "Testbed to create"
// @Success 200 {object} model.TestBedInfo
// @Failure default {object} HTTPError
// @Router /testbeds/info [post]
func (h *HTTPHandler) CreateTestBedInfo(c echo.Context) error {
	ctx := c.(utils.Context)
	log := ctx.Log()

	var req model.TestBedInfo
	if err := ctx.Bind(&req); err != nil {
		return serror.NewMsg(http.StatusBadRequest, "request body bind model.TestBedInfo error")
	}

	log.With("testbedName", req.Name, "planExecutionID", req.PlanExecutionID).Infof("CreateTestBed")

	resp, err := h.service.Testbed.Create(ctx, &req)
	if err != nil {
		return err
	}

	return ctx.JSON(http.StatusOK, resp)
}

// UpdateTestBedInfoStatus ...
// @Summary Update testbed's dynamic info (such as tidb url/ prom url etc.)
// @ID update-testbed
// @Tags plan-execution
// @Security Bearer
// @Param id path integer true "testbed's plan_exec_id"
// @Param name path string true "testbed name"
// @Param status query string true "{'INITIATING', 'READY', 'RELEASED', 'ERROR'}"
// @Param initiated_at query integer false "timestamp when InitiatedAt"
// @Param setup_log_url query string true "URL to get testbed setup log"
// @Success 200 {object} map[string]interface{}
// @Failure default {object} HTTPError
// @Router /plan-executions/{id}/testbeds/{name}/info/status [put]
func (h *HTTPHandler) UpdateTestBedInfoStatus(c echo.Context) error {
	ctx := c.(utils.Context)
	log := ctx.Log()

	planExecID, err := utils.ParseID(ctx.Param("id"))
	if err != nil {
		return serror.NewMsg(http.StatusBadRequest, "planExecID should not be provided")
	}

	testbedName := ctx.Param("name")
	if testbedName == "" {
		return serror.NewMsg(http.StatusBadRequest, "testbedName should not be empty if without plan_exec_id")
	}

	log.With("planExecID", planExecID, "testbed", testbedName).Infof("UpdateTestBedInfoStatus")

	var initiatedAt *time.Time
	tsInitiatedAt, err := strconv.ParseInt(ctx.QueryParam("initiated_at"), 10, 64)
	if err == nil {
		tt := time.Unix(tsInitiatedAt, 0)
		initiatedAt = &tt
	}
	status := model.TestBedInfoStatus(ctx.QueryParam("status"))

	if err := h.service.Testbed.Update(ctx, common.TestbedUpdateOpts{
		Name:          testbedName,
		PlanExecID:    &planExecID,
		InitiatedAt:   initiatedAt,
		UpdatedStatus: status,
		SetUpLogURL:   ctx.QueryParam("setup_log_url"),
	}); err != nil {
		return err
	}

	return ctx.JSON(http.StatusOK, echo.Map{"result": "ok"})
}

// GetTestBed ...
// @Summary Release an alive testbed
// @ID get-testbed
// @Tags plan-execution
// @Security Bearer
// @Param id path integer true "testbed's plan_exec_id"
// @Param name path string true "testbed name"
// @Param with_resource_details query bool false "refresh testbed with resource details"
// @Success 200 {object} model.TestBedInfo
// @Failure default {object} HTTPError
// @Router /plan-executions/{id}/testbeds/{name} [get]
func (h *HTTPHandler) GetTestBed(c echo.Context) error {
	ctx := c.(utils.Context)
	log := ctx.Log()

	planExecID, err := utils.ParseID(ctx.Param("id"))
	if err != nil {
		return serror.NewMsg(http.StatusBadRequest, "planExecID should not be provided")
	}

	testbedName := ctx.Param("name")
	if testbedName == "" {
		return serror.NewMsg(http.StatusBadRequest, "testbedName should not be empty")
	}

	withResourceDetails, _ := strconv.ParseBool(ctx.QueryParam("with_resource_details"))

	log.With("testbedName", testbedName, "withResourceDetails", withResourceDetails).Infof("GetTestbed")

	res, err := h.service.Testbed.Get(ctx, planExecID, testbedName, withResourceDetails)
	if err != nil {
		return err
	}
	return ctx.JSON(http.StatusOK, res)
}

// RenewTestBed ...
// @Summary Release an alive testbed
// @ID renew-testbed
// @Tags plan-execution
// @Security Bearer
// @Param id path integer true "testbed's plan_exec_id"
// @Param name path string true "testbed name"
// @Param lease query string true "lease to set"
// @Success 200 {object} model.TestBedInfo
// @Failure default {object} HTTPError
// @Router /plan-executions/{id}/testbeds/{name}/renew [put]
func (h *HTTPHandler) RenewTestBed(c echo.Context) error {
	ctx := c.(utils.Context)
	log := ctx.Log()

	planExecID, err := utils.ParseID(ctx.Param("id"))
	if err != nil {
		return serror.NewMsg(http.StatusBadRequest, "planExecID should not be provided")
	}

	testbedName := ctx.Param("name")
	if testbedName == "" {
		return serror.NewMsg(http.StatusBadRequest, "testbedName should not be empty")
	}

	lease := ctx.QueryParam("lease")

	log.With("testbed", testbedName, "lease", lease).Infof("renewTestBed")
	if err := h.service.Testbed.Renew(ctx, planExecID, testbedName, lease); err != nil {
		return err
	}

	return h.GetTestBed(c)
}

// ReleaseTestBed ...
// @Summary Release an alive testbed
// @ID release-testbed
// @Tags plan-execution
// @Security Bearer
// @Param id path integer true "testbed's plan_exec_id"
// @Param name path string true "testbed name"
// @Success 200 {object} model.TestBedInfo
// @Failure default {object} HTTPError
// @Router /plan-executions/{id}/testbeds/{name}/release [put]
func (h *HTTPHandler) ReleaseTestBed(c echo.Context) error {
	ctx := c.(utils.Context)
	log := ctx.Log()

	planExecID, err := utils.ParseID(ctx.Param("id"))
	if err != nil {
		return serror.NewMsg(http.StatusBadRequest, "planExecID should not be provided")
	}

	testbedName := ctx.Param("name")
	if testbedName == "" {
		return serror.NewMsg(http.StatusBadRequest, "testbedName should not be empty")
	}

	log.With("testbed", testbedName).Infof("releaseTestBed")
	if err := h.service.Testbed.Release(ctx, planExecID, testbedName); err != nil {
		return err
	}

	return h.GetTestBed(c)
}

// RestoreMonitor create a testbed with grafana and prometheus, and the data is restored from backup tarball
// @Summary RestoreMonitor of a dead testbed
// @ID restore-monitor
// @Tags plan-execution
// @Security Bearer
// @Param id path integer true "testbed's plan_exec_id"
// @Param name path string true "testbed name"
// @Success 200 {object} model.TestBedInfo
// @Failure default {object} HTTPError
// @Router /plan-executions/{id}/testbeds/{name}/restore-monitor [put]
func (h *HTTPHandler) RestoreMonitor(c echo.Context) error {
	ctx := c.(utils.Context)
	log := ctx.Log()

	planExecID, err := utils.ParseID(ctx.Param("id"))
	if err != nil {
		return serror.NewMsg(http.StatusBadRequest, "planExecID should not be provided")
	}

	testbedName := ctx.Param("name")
	if testbedName == "" {
		return serror.NewMsg(http.StatusBadRequest, "testbedName should not be empty")
	}

	log.With("testbed", testbedName).Infof("RestoreMonitor")
	restoredTestbed, err := h.service.Testbed.RestoreMonitor(ctx, planExecID, testbedName, "k8s", "")
	if err != nil {
		return err
	}

	res, err := h.service.Testbed.Get(ctx, planExecID, restoredTestbed.Name, true)
	if err != nil {
		return err
	}
	return ctx.JSON(http.StatusOK, res)
}
