package handler

import (
	"net/http"
	"strconv"

	"github.com/creasty/defaults"
	"github.com/labstack/echo/v4"

	"github.com/pingcap/test-infra/common/errors"
	"github.com/pingcap/test-infra/common/model/rms/request"
	"github.com/pingcap/test-infra/common/model/rms/response"
	"github.com/pingcap/test-infra/services/rms/utils"
)

var (
	_ = request.CreateTestbedRequest{}
	_ = response.CreateTestbedResponse{}
	_ = errors.StatusError{}
)

// CreateTestbed ...
// @Summary Create the testbed
// @ID create-testbed
// @Tags testbeds
// @Security Bearer
// @Accept json
// @Produce json
// @Param testbed body request.CreateTestbedRequest true "Testbed to create"
// @Success 200 {object} response.CreateTestbedResponse
// @Failure 500 {object} errors.StatusError
// @Router /testbeds [post]
func (h Handler) CreateTestbed(c echo.Context) error {
	ctx := c.(utils.Context)

	testbed := &request.CreateTestbedRequest{}
	if err := ctx.Bind(testbed); err != nil {
		ctx.Log().Errorf(err, "createTestbed parse resource")
		return errors.NewBadRequest(err.Error())
	}
	if err := defaults.Set(testbed); err != nil {
		ctx.Log().Errorf(err, "createTestbed default resource")
		return errors.NewBadRequest(err.Error())
	}
	if err := testbed.Validate(); err != nil {
		ctx.Log().Errorf(err, "createTestbed validate resource")
		return errors.NewBadRequest(err.Error())
	}
	resp, statusErr := h.Service.CreateTestbed(ctx, *testbed)
	if statusErr != nil {
		ctx.Log().Errorf(statusErr, "create testbed")
		return statusErr
	}
	return ctx.JSON(http.StatusOK, resp)
}

// ListTestbed ...
// @Summary list testbeds
// @ID list-testbed
// @Tags testbeds
// @Security Bearer
// @Accept json
// @Produce json
// @Success 200 {object} response.ListTestbedResponse
// @Failure 400 {object} errors.StatusError
// @Failure 500 {object} errors.StatusError
// @Router /testbeds [get]
func (h Handler) ListTestbed(c echo.Context) error {
	ctx := c.(utils.Context)
	resp, err := h.Service.ListTestbed(ctx)
	if err != nil {
		ctx.Log().Errorf(err, "ListTestbed error")
		return err
	}
	return ctx.JSON(http.StatusOK, resp)
}

func (h Handler) GetTestbedDeprecated(c echo.Context) error {
	ctx := c.(utils.Context)

	testbed := &request.GetTestbedRequest{}
	if err := ctx.Bind(testbed); err != nil {
		ctx.Log().Errorf(err, "GetTestbed parse resource")
		return errors.NewBadRequest(err.Error())
	}
	if err := testbed.Validate(); err != nil {
		ctx.Log().Errorf(err, "GetTestbed validate resource")
		return errors.NewBadRequest(err.Error())
	}
	resp, err := h.Service.GetTestbed(ctx, testbed.Name, false)
	if err != nil {
		ctx.Log().Errorf(err, "GetTestbed error")
		return err
	}
	return ctx.JSON(http.StatusOK, resp)
}

// GetShootKubeConfig ...
// @Summary Get kubeconfig of tidbcloud's shoot cluster
// @ID get-shoot-kubeconfig
// @Tags testbeds
// @Security Bearer
// @Accept json
// @Produce json
// @Param name path string true "Testbed the TiDBCloudCluster belongs to"
// @Success 200 {object} response.GetShootKubeConfigResponse
// @Failure 500 {object} errors.StatusError
// @Router /testbeds/:name/shoot-kubeconfig [get]
func (h Handler) GetShootKubeConfig(c echo.Context) error {
	ctx := c.(utils.Context)

	testbedName := ctx.Param("name")
	if testbedName == "" {
		return errors.NewBadRequest("testbed name should not be empty")
	}

	req := &request.GetShootKubeConfigRequest{}
	if err := ctx.Bind(req); err != nil {
		ctx.Log().Errorf(err, "GetShootKubeConfig parse resource")
		return errors.NewBadRequest(err.Error())
	}
	if err := req.Validate(); err != nil {
		ctx.Log().Errorf(err, "GetShootKubeConfig validate resource")
		return errors.NewBadRequest(err.Error())
	}

	resp, err := h.Service.GetShootKubeConfig(ctx, *req)
	if err != nil {
		ctx.Log().Errorf(err, "GetShootKubeConfig")
		return err
	}

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

// GetTestbed ...
// @Summary Get the testbed basic info
// @ID get-testbed
// @Tags testbeds
// @Security Bearer
// @Accept json
// @Produce json
// @Param name path string true "Testbed to get"
// @Param with_details query bool false "return with details"
// @Success 200 {object} response.GetTestbedResponse
// @Failure 400 {object} errors.StatusError
// @Failure 500 {object} errors.StatusError
// @Router /testbeds/:name [get]
func (h Handler) GetTestbed(c echo.Context) error {
	ctx := c.(utils.Context)

	testbedName := ctx.Param("name")
	if testbedName == "" {
		return errors.NewBadRequest("testbed name should not be empty")
	}

	withDetails, _ := strconv.ParseBool(ctx.QueryParam("with_details"))

	resp, err := h.Service.GetTestbed(ctx, testbedName, withDetails)
	if err != nil {
		ctx.Log().With("testbed", testbedName).Errorf(err, "GetTestbed error")
		return err
	}
	return ctx.JSON(http.StatusOK, resp)
}

// CheckTestbed ...
// @Summary check the testbed status
// @ID check-testbed
// @Tags testbeds
// @Security Bearer
// @Produce json
// @Param name path string true "Testbed to check"
// @Success 200 {object} response.CheckTestbedResponse
// @Failure 400 {object} errors.StatusError
// @Failure 500 {object} errors.StatusError
// @Router /testbeds/:name/check [get]
func (h Handler) CheckTestbed(c echo.Context) error {
	ctx := c.(utils.Context)

	testbedName := ctx.Param("name")
	if testbedName == "" {
		return errors.NewBadRequest("testbed name should not be empty")
	}

	resp, err := h.Service.CheckTestbed(ctx, testbedName)
	if err != nil {
		ctx.Log().With("testbed", testbedName).Errorf(err, "CheckTestbed error")
		return err
	}
	return ctx.JSON(http.StatusOK, resp)
}

// RestoreMonitor ...
// @Summary restore monitor from prometheus backup
// @ID restore-monitor
// @Tags testbeds
// @Security Bearer
// @Accept json
// @Produce json
// @Param name path string true "Testbed to restore monitor"
// @Param testbed body request.RestoreMonitorRequest true "restore monitor request"
// @Success 200 {object} string
// @Failure 400 {object} errors.StatusError
// @Failure 500 {object} errors.StatusError
// @Router /testbeds/:name/restore-monitor [post]
func (h Handler) RestoreMonitor(c echo.Context) error {
	ctx := c.(utils.Context)

	req := &request.RestoreMonitorRequest{}
	if err := ctx.Bind(req); err != nil {
		ctx.Log().Errorf(err, "AuthTestbed parse resource")
		return errors.NewBadRequest(err.Error())
	}
	if err := req.Validate(); err != nil {
		ctx.Log().Errorf(err, "AuthTestbed validate resource")
		return errors.NewBadRequest(err.Error())
	}

	resp, err := h.Service.RestoreMonitor(ctx, *req)
	if err != nil {
		ctx.Log().With("testbed", req.Testbed, "promTarballURL", req.PrometheusTarballURL).Errorf(err, "RestoreMonitor")
		return err
	}

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

// AuthTestbed ...
// @Summary Authenticate the testbed to user(s)
// @ID auth-testbed
// @Tags testbeds
// @Security Bearer
// @Accept json
// @Param name path string true "Testbed to auth"
// @Param user body request.AuthTestbedRequest true "authTestbed Request"
// @Success 200 {object} map[string]interface{}
// @Failure 400 {object} errors.StatusError
// @Failure 404 {object} errors.StatusError
// @Failure 500 {object} errors.StatusError
// @Router /testbeds/:name/auth [put]
func (h Handler) AuthTestbed(c echo.Context) error {
	ctx := c.(utils.Context)

	testbedName := ctx.Param("name")
	if testbedName == "" {
		return errors.NewBadRequest("testbed name should not be empty")
	}

	req := &request.AuthTestbedRequest{}
	if err := ctx.Bind(req); err != nil {
		ctx.Log().Errorf(err, "AuthTestbed parse resource")
		return errors.NewBadRequest(err.Error())
	}
	req.Name = testbedName
	if err := req.Validate(); err != nil {
		ctx.Log().Errorf(err, "AuthTestbed validate resource")
		return errors.NewBadRequest(err.Error())
	}

	err := h.Service.AuthTestbed(ctx, *req)
	if err != nil {
		ctx.Log().With("testbed", testbedName).Errorf(err, "AuthTestbed error")
		return err
	}
	return ctx.JSON(http.StatusOK, echo.Map{"result": "ok"})
}

// DeleteTestbed ...
// @Summary delete the testbed
// @ID delete-testbed
// @Tags testbeds
// @Security Bearer
// @Accept json
// @Produce json
// @Param name path string true "Testbed to delete"
// @Success 200 {object} map[string]interface{}
// @Failure 400 {object} errors.StatusError
// @Failure 404 {object} errors.StatusError
// @Failure 500 {object} errors.StatusError
// @Router /testbeds/:name [delete]
func (h Handler) DeleteTestbed(c echo.Context) error {
	ctx := c.(utils.Context)

	testbedName := ctx.Param("name")
	if testbedName == "" {
		return errors.NewBadRequest("testbed name should not be empty")
	}

	if statusErr := h.Service.DeleteTestbed(ctx, testbedName); statusErr != nil {
		return statusErr
	}
	return ctx.JSON(http.StatusOK, echo.Map{"result": "ok"})
}

// CompleteTestbed ...
// @Summary complete the testbed
// @ID complete-testbed
// @Tags testbeds
// @Security Bearer
// @Accept json
// @Produce json
// @Param name path string true "Testbed to complete"
// @Param type query string true "On Testbed Completion Type"
// @Success 200 {object} map[string]interface{}
// @Failure 400 {object} errors.StatusError
// @Failure 404 {object} errors.StatusError
// @Failure 500 {object} errors.StatusError
// @Router /testbeds/:name/complete [put]
func (h Handler) CompleteTestbed(c echo.Context) error {
	ctx := c.(utils.Context)

	testbedName := ctx.Param("name")
	if testbedName == "" {
		return errors.NewBadRequest("testbed name should not be empty")
	}

	ty := request.OnTestbedCompletionType(ctx.QueryParam("type"))
	if err := ty.Validate(); err != nil {
		ctx.Log().Errorf(err, "completeTestbed validate completion type")
		return errors.NewBadRequest(err.Error())
	}

	// temporary solution to pass clinicURL for sending notification.
	// TODO: move notification to TCMS
	testbed := &request.OnTestbedCompletionRequest{}
	_ = ctx.Bind(testbed) // request body can be empty(and that's valid)

	statusErr := h.Service.OnCompletion(ctx, request.OnTestbedCompletionRequest{
		Name:       testbedName,
		Type:       ty,
		ClinicURLs: testbed.ClinicURLs,
	})
	if statusErr != nil {
		return statusErr
	}
	return ctx.JSON(http.StatusOK, echo.Map{"result": "ok"})
}

func (h Handler) AuthTestbedDeprecated(c echo.Context) error {
	ctx := c.(utils.Context)

	testbed := &request.AuthTestbedRequest{}
	if err := ctx.Bind(testbed); err != nil {
		ctx.Log().Errorf(err, "AuthTestbed parse resource")
		return errors.NewBadRequest(err.Error())
	}
	if err := testbed.Validate(); err != nil {
		ctx.Log().Errorf(err, "AuthTestbed validate resource")
		return errors.NewBadRequest(err.Error())
	}

	err := h.Service.AuthTestbed(ctx, *testbed)
	if err != nil {
		ctx.Log().Errorf(err, "AuthTestbed error")
		return err
	}
	return ctx.JSON(http.StatusOK, "")
}

func (h Handler) DeleteTestbedDeprecated(c echo.Context) error {
	ctx := c.(utils.Context)

	testbed := &request.DeleteTestbedRequest{}
	if err := ctx.Bind(testbed); err != nil {
		ctx.Log().Errorf(err, "DeleteTestbed parse resource")
		return errors.NewBadRequest(err.Error())
	}
	if err := testbed.Validate(); err != nil {
		ctx.Log().Errorf(err, "DeleteTestbed validate resource")
		return errors.NewBadRequest(err.Error())
	}
	if statusErr := h.Service.DeleteTestbed(ctx, testbed.Name); statusErr != nil {
		return statusErr
	}
	return ctx.JSON(http.StatusOK, "")
}

func (h Handler) CompleteTestbedDeprecated(c echo.Context) error {
	ctx := c.(utils.Context)

	req := &request.OnTestbedCompletionRequest{}
	if err := ctx.Bind(req); err != nil {
		ctx.Log().Errorf(err, "completeTestbed parse resource")
		return errors.NewBadRequest(err.Error())
	}
	if err := req.Validate(); err != nil {
		ctx.Log().Errorf(err, "completeTestbed validate resource")
		return errors.NewBadRequest(err.Error())
	}
	statusErr := h.Service.OnCompletion(ctx, *req)
	if statusErr != nil {
		return statusErr
	}
	return ctx.JSON(http.StatusOK, "")
}

// GetResourcePoolStatus ...
// @Summary get resource pool status
// @ID get-resource-pool-status
// @Tags resource-pool
// @Accept json
// @Produce json
// @Param resource body request.GetResourcePoolStatusRequest true "ResourcePool to get status"
// @Success 200 {object} response.GetResourcePoolStatusResponse
// @Failure 400 {object} errors.StatusError
// @Failure 500 {object} errors.StatusError
// @Router /resource-pool/status [get]
func (h Handler) GetResourcePoolStatus(c echo.Context) error {
	ctx := c.(utils.Context)
	resourcePool := &request.GetResourcePoolStatusRequest{}

	if err := ctx.Bind(resourcePool); err != nil {
		ctx.Log().Errorf(err, "GetResourcePoolStatus parse")
		return errors.NewBadRequest(err.Error())
	}
	if err := resourcePool.Validate(); err != nil {
		ctx.Log().Errorf(err, "GetResourcePoolStatus validate")
		return errors.NewBadRequest(err.Error())
	}

	resp, err := h.StatisticsService.GetResourcePoolStatus(ctx, *resourcePool)
	if err != nil {
		ctx.Log().Errorf(err, "GetResourcePoolStatus")
		return err
	}

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

// GetTestbedsResource ...
// @Summary get testbed resource
// @ID get-testbed-resource
// @Tags resource-pool
// @Accept json
// @Produce json
// @Param resource body request.GetTestbedsResourceRequest true "ResourcePool to get testbeds resource"
// @Success 200 {object} response.GetTestbedsResourceResponse
// @Failure 400 {object} errors.StatusError
// @Failure 500 {object} errors.StatusError
// @Router /resource-pool/testbeds-resource [get]
func (h Handler) GetTestbedsResource(c echo.Context) error {
	ctx := c.(utils.Context)
	resourcePool := &request.GetTestbedsResourceRequest{}

	if err := ctx.Bind(resourcePool); err != nil {
		ctx.Log().Errorf(err, "GetTestbedsResource parse")
		return errors.NewBadRequest(err.Error())
	}
	if err := resourcePool.Validate(); err != nil {
		ctx.Log().Errorf(err, "GetTestbedsResource validate")
		return errors.NewBadRequest(err.Error())
	}

	resp, err := h.StatisticsService.GetTestbedsResource(ctx, *resourcePool)
	if err != nil {
		ctx.Log().Errorf(err, "GetTestbedsResource")
		return err
	}

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

// GetTestbedResourceOvertime ...
// @Summary get testbed resource overtime
// @ID get-testbed-resource-overtime
// @Tags resource-pool
// @Accept json
// @Produce json
// @Param resource body request.GetTestbedResourceOvertimeRequest true "testbed to get resource"
// @Success 200 {object} response.GetTestbedResourceOvertimeResponse
// @Failure 400 {object} errors.StatusError
// @Failure 500 {object} errors.StatusError
// @Router /resource-pool/testbed-resource-overtime [get]
func (h Handler) GetTestbedResourceOvertime(c echo.Context) error {
	ctx := c.(utils.Context)
	req := &request.GetTestbedResourceOvertimeRequest{}

	if err := ctx.Bind(req); err != nil {
		ctx.Log().Errorf(err, "GetTestbedsResource parse")
		return errors.NewBadRequest(err.Error())
	}
	if err := req.Validate(); err != nil {
		ctx.Log().Errorf(err, "GetTestbedsResource validate")
		return errors.NewBadRequest(err.Error())
	}

	resp, err := h.StatisticsService.GetTestbedResourceOvertime(ctx, *req)
	if err != nil {
		ctx.Log().Errorf(err, "GetTestbedResourceOvertime")
		return err
	}

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

// GenerateCredential ...
// @Summary generate credential for user at this cluster
// @ID generate-credential
// @Tags credential
// @Security Bearer
// @Accept json
// @Produce json
// @Param resource body request.CredentialRequest true "provided public info"
// @Success 200 {object} string
// @Failure 400 {object} errors.StatusError
// @Failure 404 {object} errors.StatusError
// @Failure 500 {object} errors.StatusError
// @Router /credential/ [post]
func (h Handler) GenerateCredential(c echo.Context) error {
	ctx := c.(utils.Context)
	user := utils.GetUser(ctx)
	if user == nil {
		return errors.NewForbidden("unauthenticated")
	}
	req := &request.CredentialRequest{}
	if err := ctx.Bind(req); err != nil {
		ctx.Log().Errorf(err, "GenerateCredential Bind content string")
		return errors.NewBadRequest(err.Error())
	}
	if err := req.Validate(); err != nil {
		ctx.Log().Errorf(err, "GenerateCredential validate")
		return errors.NewBadRequest(err.Error())
	}

	resp, err := h.Service.GenerateCredential(ctx, *user, *req)
	if err != nil {
		return err
	}
	return ctx.JSON(http.StatusOK, resp)
}

// GetTemporaryECRImageSyncCredential ...
// @Summary get the temporary ecr image sync credential
// @ID get-temporary-ecr-image-sync-credential
// @Tags credentials
// @Produce json
// @Success 200 {object} response.GetTemporaryECRImageSyncCredentialResponse
// @Router /credential/aws-ecr [get]
func (h Handler) GetTemporaryECRImageSyncCredential(c echo.Context) error {
	ctx := c.(utils.Context)
	resp, err := h.Service.GetTemporaryECRImageSyncCredential(ctx)
	if err != nil {
		return err
	}
	return ctx.JSON(http.StatusOK, resp)
}

func (h Handler) RenewalLeaseDeprecated(c echo.Context) error {
	ctx := c.(utils.Context)
	user := utils.GetUser(ctx)
	if user == nil {
		return errors.NewForbidden("unauthenticated")
	}
	var req request.RenewalLeaseRequest
	if err := ctx.Bind(&req); err != nil {
		ctx.Log().Errorf(err, "GenerateCredential Bind content string")
		return errors.NewBadRequest(err.Error())
	}
	if err := req.Validate(); err != nil {
		ctx.Log().Errorf(err, "RenewalLease validate")
		return errors.NewBadRequest(err.Error())
	}

	resp, err := h.Service.RenewLease(ctx, req.Name, req.Lease)
	if err != nil {
		return err
	}
	return ctx.JSON(http.StatusOK, resp)
}

// GetLease ...
// @Summary get the testbed lease
// @ID get-testbed-lease
// @Tags testbeds
// @Security Bearer
// @Produce json
// @Param name path string true "Testbed to get lease"
// @Success 200 {object} response.LeaseResponse
// @Failure 400 {object} errors.StatusError
// @Failure 500 {object} errors.StatusError
// @Router /testbeds/:name/lease [get]
func (h Handler) GetLease(c echo.Context) error {
	ctx := c.(utils.Context)

	testbedName := ctx.Param("name")
	if testbedName == "" {
		return errors.NewBadRequest("testbed name should not be empty")
	}

	resp, err := h.Service.GetLease(ctx, testbedName)
	if err != nil {
		ctx.Log().Errorf(err, "GetLease error")
		return err
	}
	return ctx.JSON(http.StatusOK, resp)
}

// RenewLease ...
// @Summary RenewLease renew lease of a testbed
// @ID renew-lease
// @Tags testbeds
// @Security Bearer
// @Produce json
// @Param name path string true "Testbed to renew lease"
// @Param lease query string true "Lease to renew, testbed DDL will be updated to 'time.Now() + lease'"
// @Success 200 {object} response.RenewalLeaseResponse
// @Failure 404 {object} errors.StatusError
// @Failure 500 {object} errors.StatusError
// @Router /testbeds/:name/lease/renew [put]
func (h Handler) RenewLease(c echo.Context) error {
	ctx := c.(utils.Context)
	user := utils.GetUser(ctx)
	if user == nil {
		return errors.NewForbidden("unauthenticated")
	}

	testbedName := ctx.Param("name")
	if testbedName == "" {
		return errors.NewBadRequest("testbed name should not be empty")
	}
	lease := request.DurationStr(ctx.QueryParam("lease"))
	if err := lease.Validate(); err != nil {
		ctx.Log().Errorf(err, "lease validate error")
		return errors.NewBadRequest(err.Error())
	}

	resp, err := h.Service.RenewLease(ctx, testbedName, lease)
	if err != nil {
		ctx.Log().Errorf(err, "RenewLease error")
		return err
	}
	return ctx.JSON(http.StatusOK, resp)
}

// GetLogLinks ...
// @Summary get the testbed log links
// @ID get-testbed-log-links
// @Tags testbeds
// @Security Bearer
// @Produce json
// @Param name path string true "Testbed to get log links"
// @Success 200 {object} response.GetLogLinksResponse
// @Failure 400 {object} errors.StatusError
// @Failure 500 {object} errors.StatusError
// @Router /testbeds/:name/log_links [get]
func (h Handler) GetLogLinks(c echo.Context) error {
	ctx := c.(utils.Context)

	testbedName := ctx.Param("name")
	if testbedName == "" {
		return errors.NewBadRequest("testbed name should not be empty")
	}

	resp, err := h.Service.GetLogLinks(ctx, testbedName)
	if err != nil {
		ctx.Log().Errorf(err, "GetLogLinks error")
		return err
	}
	return ctx.JSON(http.StatusOK, resp)
}

// GetProxy ...
// @Summary get the proxy
// @ID get-proxy
// @Tags proxy
// @Produce json
// @Success 200 {object} map[string]interface{}
// @Router /proxy [get]
func (h Handler) GetProxy(c echo.Context) error {
	ctx := c.(utils.Context)
	user := utils.GetUser(ctx)
	if user == nil {
		return errors.NewForbidden("unauthenticated")
	}
	resp, err := h.Service.GetProxy(ctx)
	if err != nil {
		return err
	}
	return ctx.JSON(http.StatusOK, resp)
}
