package customstatus

import (
	"context"
	"errors"

	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
	"google.golang.org/protobuf/proto"
	"google.golang.org/protobuf/types/known/emptypb"
	"gorm.io/gorm"

	tidbmgmtservicev1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_mgmt_service/v1"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/constant"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/entity"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/pkg/apimiddleware/bizerror"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
)

func (c serverImpl) SetClusterCustomStatus(
	ctx context.Context,
	request *tidbmgmtservicev1.SetClusterCustomStatusRequest,
) (*tidbmgmtservicev1.ClusterCustomStatus, error) {
	if request.CustomStatus == nil {
		return nil, bizerr.New(bizerr.ErrCodeParams, "custom status is invalid")
	}

	if request.CustomStatus.StatusType == tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_UNSPECIFIED {
		return nil, bizerr.New(bizerr.ErrCodeParams, "custom status type is unspecified")
	}

	statusEntity, mutexCustomStatus, mutexState, err := c.customStatusSvc.SetClusterCustomStatus(
		ctx,
		cvtCustomStatusEntity(request.CustomStatus),
	)
	if err != nil {
		if errors.Is(err, constant.ErrClusterNotFound) {
			return nil, bizerr.WrapErrorWithCode(err, bizerr.GrayscaleUpgradeDataNotInTargetSystem, "cluster not found")
		}
		if errors.Is(err, constant.ErrClusterCustomStatusForbidden) {
			if mutexCustomStatus == nil {
				return nil, bizerror.NewForbiddenForClusterCustomStatus(err, mutexState.String())
			}
			return nil, bizerror.NewForbiddenForClusterCustomStatus(err, mutexCustomStatus.StatusType.String())
		}
		return nil, bizerror.NewInternal(err, "failed to set cluster custom status")
	}

	return CvtCustomStatusMgmt(statusEntity), nil
}

func (c serverImpl) GetClusterCustomStatusByID(
	ctx context.Context,
	request *tidbmgmtservicev1.GetClusterCustomStatusByIDRequest,
) (*tidbmgmtservicev1.ClusterCustomStatus, error) {
	// check cluster id
	if request.ClusterId == 0 {
		return nil, bizerr.New(bizerr.ErrCodeParams, "cluster id is invalid")
	}

	if request.StatusId == 0 {
		return nil, bizerr.New(bizerr.ErrCodeParams, "cluster custom status id is invalid")
	}

	statusEntity, err := c.customStatusSvc.GetClusterCustomStatusByID(ctx, request.ClusterId, request.StatusId)
	if err != nil {
		if errors.Is(err, constant.ErrClusterNotFound) {
			return nil, bizerr.WrapErrorWithCode(err, bizerr.GrayscaleUpgradeDataNotInTargetSystem, "cluster not found")
		} else if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, bizerror.NewNotFound(err, "cluster custom status not found")
		}
		return nil, bizerror.NewInternal(err, err.Error())
	}

	return CvtCustomStatusMgmt(statusEntity), nil
}

func (c serverImpl) GetClusterCustomStatusByName(
	ctx context.Context,
	request *tidbmgmtservicev1.GetClusterCustomStatusByNameRequest,
) (*tidbmgmtservicev1.ClusterCustomStatus, error) {
	// check cluster id
	if request.ClusterId == 0 {
		return nil, bizerr.New(bizerr.ErrCodeParams, "cluster id is invalid")
	}

	if request.StatusName == "" ||
		request.StatusType == tidbmgmtservicev1.CustomStatusType_CUSTOM_STATUS_TYPE_UNSPECIFIED {
		return nil, bizerr.New(bizerr.ErrCodeParams, "cluster custom status type and name is invalid")
	}

	statusEntity, err := c.customStatusSvc.GetClusterCustomStatusByName(
		ctx,
		request.ClusterId,
		request.StatusName,
		request.StatusType,
	)
	if err != nil {
		if errors.Is(err, constant.ErrClusterNotFound) {
			return nil, bizerr.WrapErrorWithCode(err, bizerr.GrayscaleUpgradeDataNotInTargetSystem, "cluster not found")
		} else if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, bizerror.NewNotFound(err, "cluster custom status not found")
		}
		return nil, bizerror.NewInternal(err, "failed to get cluster custom status")
	}

	return CvtCustomStatusMgmt(statusEntity), nil
}

func (c serverImpl) DeleteClusterCustomStatus(
	ctx context.Context,
	request *tidbmgmtservicev1.DeleteClusterCustomStatusRequest,
) (*emptypb.Empty, error) {
	// check cluster id
	if request.ClusterId == 0 {
		return nil, bizerr.New(bizerr.ErrCodeParams, "cluster id is invalid")
	}

	if request.StatusId == 0 {
		return nil, bizerr.New(bizerr.ErrCodeParams, "cluster custom status id is invalid")
	}

	err := c.customStatusSvc.DeleteClusterCustomStatus(ctx, request.ClusterId, request.StatusId)
	if err != nil {
		if errors.Is(err, constant.ErrClusterNotFound) {
			return nil, bizerr.WrapErrorWithCode(err, bizerr.GrayscaleUpgradeDataNotInTargetSystem, "cluster not found")
		} else if errors.Is(err, gorm.ErrRecordNotFound) {
			// already deleted
			return &emptypb.Empty{}, nil
		}
		return nil, bizerror.NewInternal(err, "failed to delete cluster custom status")
	}

	return &emptypb.Empty{}, nil
}

func (c serverImpl) ListClusterCustomStatus(
	ctx context.Context,
	request *tidbmgmtservicev1.ListClusterCustomStatusRequest,
) (*tidbmgmtservicev1.ListClusterCustomStatusResponse, error) {
	if request.IncludeDeleted == nil {
		request.IncludeDeleted = proto.Bool(false)
	}
	statuesMap, err := c.customStatusSvc.ListClusterCustomStatus(
		ctx,
		request.ClusterIds,
		request.StatusTypes,
		*request.IncludeDeleted,
	)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			tilog.Warn(ctx, "cluster custom status not found",
				zap.Any("cluster_ids", request.ClusterIds),
			)
		}
		return nil, bizerror.NewInternal(err, "failed to get cluster custom status")
	}

	statusesMgmt := make([]*tidbmgmtservicev1.ClusterCustomStatus, 0)
	for _, statuses := range statuesMap {
		for _, status := range statuses {
			statusesMgmt = append(statusesMgmt, CvtCustomStatusMgmt(status))
		}
	}

	return &tidbmgmtservicev1.ListClusterCustomStatusResponse{CustomStatuses: statusesMgmt}, nil
}

func (c serverImpl) IsClusterCustomStatusExisted(
	ctx context.Context,
	request *tidbmgmtservicev1.IsClusterCustomStatusExistedRequest,
) (*tidbmgmtservicev1.IsClusterCustomStatusExistedResponse, error) {
	if request.ClusterId == 0 {
		return nil, bizerr.New(bizerr.ErrCodeParams, "cluster id is invalid")
	}

	statusMap, err := c.customStatusSvc.ListClusterCustomStatus(ctx,
		[]uint64{request.ClusterId}, []tidbmgmtservicev1.CustomStatusType{request.StatusType}, false)
	if err != nil {
		if errors.Is(err, constant.ErrClusterNotFound) {
			return nil, bizerr.WrapErrorWithCode(err, bizerr.GrayscaleUpgradeDataNotInTargetSystem, "cluster not found")
		} else if errors.Is(err, gorm.ErrRecordNotFound) {
			return &tidbmgmtservicev1.IsClusterCustomStatusExistedResponse{IsExisted: false}, nil
		}
		return nil, bizerror.NewInternal(err, "failed to get cluster custom status")
	}

	statusesEntity := statusMap[request.ClusterId]
	// cvt status entity to mgmt service response
	var statusesMgmt []*tidbmgmtservicev1.ClusterCustomStatus
	for _, status := range statusesEntity {
		statusesMgmt = append(statusesMgmt, CvtCustomStatusMgmt(status))
	}

	return &tidbmgmtservicev1.IsClusterCustomStatusExistedResponse{
		IsExisted:             len(statusesEntity) > 0,
		ExistedCustomStatuses: statusesMgmt,
	}, nil
}

func cvtCustomStatusEntity(status *tidbmgmtservicev1.ClusterCustomStatus) *entity.ClusterCustomStatus {
	return &entity.ClusterCustomStatus{
		ClusterId:     status.ClusterId,
		StatusType:    status.StatusType,
		StatusName:    status.StatusName,
		CustomMutex:   status.Mutex,
		TimeoutSecs:   status.TimeoutSecs,
		OverrideState: status.OverrideStatus,
	}
}

func CvtCustomStatusMgmt(status *entity.ClusterCustomStatus) *tidbmgmtservicev1.ClusterCustomStatus {
	return &tidbmgmtservicev1.ClusterCustomStatus{
		StatusId:       status.StatusId,
		ClusterId:      status.ClusterId,
		StatusType:     status.StatusType,
		StatusName:     status.StatusName,
		Mutex:          status.CustomMutex,
		TimeoutSecs:    status.TimeoutSecs,
		OverrideStatus: status.OverrideState,
		IsExpired:      status.IsExpired(),
	}
}
