package api

import (
	"net/http"

	"github.com/gin-gonic/gin"
	"github.com/joomcode/errorx"
	"github.com/pingcap-inc/clinic/pkg/auth/session"
	"github.com/pingcap-inc/clinic/pkg/db/model"
	"github.com/pingcap/errors"
	"github.com/pingcap/log"
	"github.com/tidbcloud/chaos-svc/idl/pbgen/proto/chaos"
	"go.uber.org/zap"

	"github.com/pingcap-inc/chaosAPI/pkg/infra/client"
	"github.com/pingcap-inc/chaosAPI/pkg/wrapper/ginWrapper"
)

// EnableFaultDrill
// @Summary Enable fault drill
// @Tags Chaos
// @Description Enable fault drill
// @Produce json
// @Param oid path string true "organization id"
// @Param cid path string true "cluster id"
// @Success 200 {object} nil
// @Failure 400 {object} error
// @Failure 500 {object} error
// @Router /fault/api/orgs/{oid}/clusters/{cid} [post]
func (s *Service) EnableFaultDrill(c *gin.Context) {
	args, err := GetArgs(c)
	if err != nil {
		ginWrapper.BadRequest(c, errorx.ExternalError.New("%s", err.Error()))
		return
	}
	rsp, _ := s.ChaosSvc.EnableChaosBackend(c, &chaos.EnableChaosBackendRequest{
		ClusterId: args.ClusterID,
		ProjectId: args.ProjectID,
		UserId:    args.UserID,
	})
	if client.ToGRPCError(rsp.GetBaseResp()) != nil {
		ginWrapper.InternalServerError(c, client.ToGRPCError(rsp.GetBaseResp()))
		return
	}
	c.JSON(http.StatusOK, nil)
}

// DisableFaultDrill
// @Summary Disable fault drill
// @Tags Chaos
// @Description Disable fault drill
// @Produce json
// @Param oid path string true "organization id"
// @Param cid path string true "cluster id"
// @Success 200 {object} nil
// @Failure 400 {object} error
// @Failure 500 {object} error
// @Router /fault/api/orgs/{oid}/clusters/{cid} [delete]
func (s *Service) DisableFaultDrill(c *gin.Context) {
	args, err := GetArgs(c)
	if err != nil {
		ginWrapper.BadRequest(c, errorx.ExternalError.New("%s", err.Error()))
		return
	}

	rsp, _ := s.ChaosSvc.DisableChaosBackend(c, &chaos.DisableChaosBackendRequest{
		ClusterId: args.ClusterID,
		ProjectId: args.ProjectID,
		UserId:    args.UserID,
	})

	if client.ToGRPCError(rsp.GetBaseResp()) != nil {
		ginWrapper.InternalServerError(c, client.ToGRPCError(rsp.GetBaseResp()))
		return
	}
	c.JSON(http.StatusOK, nil)
}

type BackendStatus struct {
	Status    StatusName      `json:"status" enums:"ready,disabled,installing,uninstalling,install_failed"`
	Message   string          `json:"message"`
	UpdatedAt int64           `json:"updated_at"`
	UpdatedBy session.CtxUser `json:"updated_by"`
}

type StatusName = string

const (
	ReadyStatus         StatusName = "ready"
	DisabledStatus      StatusName = "disabled"
	InstallingStatus    StatusName = "installing"
	InstallFailedStatus StatusName = "install_failed"
	UninstallingStatus  StatusName = "uninstalling"
)

var BackendStatusName = map[string]StatusName{
	chaos.ChaosBackendStatus_CHAOS_BACKEND_STATUS_READY.String():          ReadyStatus,
	chaos.ChaosBackendStatus_CHAOS_BACKEND_STATUS_DISABLED.String():       DisabledStatus,
	chaos.ChaosBackendStatus_CHAOS_BACKEND_STATUS_INSTALLING.String():     InstallingStatus,
	chaos.ChaosBackendStatus_CHAOS_BACKEND_STATUS_INSTALL_FAILED.String(): InstallFailedStatus,
	chaos.ChaosBackendStatus_CHAOS_BACKEND_STATUS_UNINSTALLING.String():   UninstallingStatus,
}

func NewBackendStatus(rsp *chaos.GetChaosBackendResp) (*BackendStatus, error) {
	rawStatus, ok := chaos.ChaosBackendStatus_name[int32(rsp.Status)]
	if !ok {
		return nil, errors.New("chaos backend status not found in chaos-svc")
	}
	status, ok := BackendStatusName[rawStatus]
	if !ok {
		return nil, errors.New("chaos backend status not found in clinic")
	}

	updater, err := model.GetUserByID(uint(rsp.UpdatedById))
	if err != nil {
		return nil, err
	}

	return &BackendStatus{
		Status:    status,
		Message:   rsp.Message,
		UpdatedAt: rsp.UpdatedAt,
		UpdatedBy: session.CtxUser{
			Username: updater.UserName,
			UserID:   uint(rsp.UpdatedById),
			Email:    updater.Email,
		},
	}, nil
}

// GetFaultDrillBackendStatus
// @Summary Get fault drill backend status
// @Tags Chaos
// @Description Get fault drill backend status
// @Produce json
// @Param oid path string true "organization id"
// @Param cid path string true "cluster id"
// @Success 200 {object} BackendStatus
// @Failure 400 {object} error
// @Failure 500 {object} error
// @Router /fault/api/orgs/{oid}/clusters/{cid} [get]
func (s *Service) GetFaultDrillBackendStatus(c *gin.Context) {
	log.Debug("get args")
	args, err := GetArgs(c)
	if err != nil {
		ginWrapper.BadRequest(c, errorx.ExternalError.New("%s", err.Error()))
		return
	}

	rsp, _ := s.ChaosSvc.GetChaosBackend(c, &chaos.GetChaosBackendRequest{
		ClusterId: args.ClusterID,
		ProjectId: args.ProjectID,
	})
	if client.ToGRPCError(rsp.GetBaseResp()) != nil {
		ginWrapper.InternalServerError(c, client.ToGRPCError(rsp.GetBaseResp()))
		return
	}
	log.Debug("get chaos backend status", zap.Any("rsp", rsp))

	log.Debug("new backend status")
	status, err := NewBackendStatus(rsp)
	if err != nil {
		ginWrapper.InternalServerError(c, errorx.ExternalError.New("%s", err.Error()))
		return
	}

	c.JSON(http.StatusOK, status)
}

// PingChaosBackend
// @Summary Ping chaos backend
// @Tags Chaos
// @Description Ping chaos backend
// @Produce json
// @Success 200 {object} nil
// @Router /fault/api/ping [get]
func (s *Service) PingChaosBackend(c *gin.Context) {
	c.JSON(http.StatusOK, nil)
}
