package backup

import (
	"context"

	"github.com/tidbcloud/core-svc/idl/pbgen/proto/core"
	"github.com/tidbcloud/dataflow-service/idl/pbgen/proto/dataflow"
	"github.com/tidbcloud/dataflow-service/internal/db/entity"
	"github.com/tidbcloud/dataflow-service/internal/openapi/service"
	"github.com/tidbcloud/dataflow-service/internal/openapi/service/backup/cvt"
	"github.com/tidbcloud/idl/pbgen/proto/openapi/dp"
	"github.com/tidbcloud/pkgv2/bizerr"
	"github.com/tidbcloud/pkgv2/tracing"
	"go.opentelemetry.io/otel"
	"go.uber.org/zap"
	"google.golang.org/protobuf/types/known/emptypb"
)

var Tracer = otel.Tracer("central/service/backup")

type Service struct {
	dp.UnimplementedBackupServer
	service.BaseService

	backupConsoleAPI dataflow.BackupServiceServer
}

func NewService(backupConsoleAPI dataflow.BackupServiceServer) *Service {
	return &Service{backupConsoleAPI: backupConsoleAPI}
}

func (s *Service) ListBackupOfCluster(ctx context.Context, req *dp.ListBackupOfClusterReq) (_ *dp.ListBackupOfClusterResp, err error) {
	ctx, span := Tracer.Start(ctx, "openapi.backup.ListBackupOfCluster")
	defer tracing.EndSpan(span, err)

	userContext := s.CurrentUser(ctx)

	cluster, err := s.checkListBackupOfCluster(ctx, userContext, req)
	if err != nil {
		return nil, err
	}

	resp, err := s.callGrpcListBackupOfCluster(ctx, userContext, req, cluster)
	if err != nil {
		return nil, err
	}
	return resp, nil
}

func (s *Service) callGrpcListBackupOfCluster(ctx context.Context, userContext *service.UserContext, req *dp.ListBackupOfClusterReq, cluster *core.Cluster) (*dp.ListBackupOfClusterResp, error) {
	backupListRequest := &dataflow.BackupListReq{
		ClusterId: req.ClusterId,
		OrgId:     cluster.TenantId,
		ProjectId: cluster.ProjectId,
		Page:      uint32(req.Page),
		PerPage:   uint32(req.PageSize),
	}
	logger := userContext.Logger()
	logger.Info("start to call list backup of cluster", zap.Uint64("clusterId", req.GetClusterId()))
	backupList, err := s.backupConsoleAPI.List(ctx, backupListRequest)
	if err != nil {
		logger.Error("call list backup of cluster occur error", zap.Uint64("clusterId", req.GetClusterId()))
		return nil, err
	}
	logger.Info("end to call grpc list backup of cluster", zap.Uint64("clusterId", req.GetClusterId()))
	resp := &dp.ListBackupOfClusterResp{
		Total: backupList.Total,
	}
	for _, item := range backupList.Items {
		covert := &dp.ListBackupItem{
			Id:              item.Id,
			Name:            item.Name,
			Description:     item.Description,
			Type:            cvt.BackupType2OpenAPI(item.Type),
			CreateTimestamp: item.CreatedAt,
			Size:            item.Size,
			Status:          cvt.BackupStatus2OpenAPIList(item.Status),
		}
		resp.Items = append(resp.Items, covert)
	}
	return resp, nil
}

func (s *Service) checkListBackupOfCluster(ctx context.Context, userContext *service.UserContext, req *dp.ListBackupOfClusterReq) (*core.Cluster, error) {
	var err error
	req.Page, err = s.CheckAndSetPageDefaultValue(req.Page)
	if err != nil {
		return nil, err
	}
	req.PageSize, err = s.CheckAndSetPageSizeDefaultValue(req.PageSize)
	if err != nil {
		return nil, err
	}
	err = userContext.CheckProjectID(entity.ID(req.ProjectId))
	if err != nil {
		return nil, err
	}
	return s.checkInfoByClusterID(ctx, userContext, req.GetClusterId())
}

func (s *Service) GetBackupOfCluster(ctx context.Context, req *dp.GetBackupOfClusterReq) (_ *dp.GetBackupOfClusterResp, err error) {
	ctx, span := Tracer.Start(ctx, "openapi.backup.GetBackupOfCluster")
	defer tracing.EndSpan(span, err)

	userContext := s.CurrentUser(ctx)

	cluster, err := s.checkGetBackupOfCluster(ctx, userContext, req)
	if err != nil {
		return nil, err
	}
	resp, err := s.callGrpcGetBackupOfCluster(ctx, userContext, req, cluster)
	if err != nil {
		return nil, err
	}
	return resp, nil
}

func (s *Service) checkGetBackupOfCluster(ctx context.Context, userContext *service.UserContext, req *dp.GetBackupOfClusterReq) (_ *core.Cluster, err error) {
	err = userContext.CheckProjectID(entity.ID(req.ProjectId))
	if err != nil {
		return nil, err
	}
	cluster, err := s.checkInfoByClusterID(ctx, userContext, req.GetClusterId())
	if err != nil {
		return nil, err
	}
	return cluster, nil
}

func (s *Service) callGrpcGetBackupOfCluster(ctx context.Context, userContext *service.UserContext,
	req *dp.GetBackupOfClusterReq, cluster *core.Cluster,
) (*dp.GetBackupOfClusterResp, error) {
	backupListRequest := &dataflow.BackupListReq{
		ClusterId: req.ClusterId,
		OrgId:     cluster.TenantId,
		ProjectId: cluster.ProjectId,
		Page:      1,
		PerPage:   1,
		BackupId:  &req.BackupId,
	}
	logger := userContext.Logger()
	logger.Info("start to call list backup of cluster",
		zap.Uint64("clusterId", req.GetClusterId()), zap.Uint64("backupId", req.GetBackupId()))
	backupList, err := s.backupConsoleAPI.List(ctx, backupListRequest)
	if err != nil {
		logger.Error("call list backup of cluster occur error",
			zap.Uint64("clusterId", req.GetClusterId()), zap.Uint64("backupId", req.GetBackupId()))
		return nil, err
	}
	logger.Info("end to call grpc list backup of cluster",
		zap.Uint64("clusterId", req.GetClusterId()), zap.Uint64("backupId", req.GetBackupId()))

	if len(backupList.Items) == 0 || backupList.Items[0].Id != req.BackupId {
		return nil, bizerr.New(bizerr.ErrCodeRecordNotFound,
			"backup is not found, cluster_id=%d, backup_id=%d", req.GetClusterId(), req.GetBackupId())
	}

	backup := backupList.Items[0]
	resp := &dp.GetBackupOfClusterResp{
		Id:              backup.Id,
		Name:            backup.Name,
		Description:     backup.Description,
		Type:            cvt.BackupType2OpenAPI(backup.Type),
		CreateTimestamp: backup.CreatedAt,
		Size:            backup.Size,
		Status:          cvt.BackupStatus2OpenAPIGet(backup.Status),
	}
	return resp, nil
}

func (s *Service) CreateBackup(ctx context.Context, req *dp.CreateBackupReq) (_ *dp.CreateBackupResp, err error) {
	ctx, span := Tracer.Start(ctx, "openapi.backup.CreateBackup")
	defer tracing.EndSpan(span, err)
	userContext := s.CurrentUser(ctx)

	cluster, err := s.checkCreateBackup(ctx, userContext, req)
	if err != nil {
		return nil, err
	}
	response, err := s.callGrpcCreateManualBackup(ctx, userContext, req, cluster)
	if err != nil {
		return nil, err
	}
	return response, nil
}

func (s *Service) checkCreateBackup(ctx context.Context, userContext *service.UserContext, req *dp.CreateBackupReq) (*core.Cluster, error) {
	err := s.CheckName(&req.Name)
	if err != nil {
		return nil, err
	}
	err = s.CheckDescription(req.Description)
	if err != nil {
		return nil, err
	}
	err = userContext.CheckProjectID(entity.ID(req.ProjectId))
	if err != nil {
		return nil, err
	}
	cluster, err := s.checkInfoByClusterID(ctx, userContext, req.GetClusterId())
	if err != nil {
		return nil, err
	}
	return cluster, nil
}

func (s *Service) callGrpcCreateManualBackup(ctx context.Context, userContext *service.UserContext, req *dp.CreateBackupReq, cluster *core.Cluster) (*dp.CreateBackupResp, error) {
	request := &dataflow.BackupCreateReq{
		ClusterId:  cluster.Id,
		OrgId:      cluster.TenantId,
		ProjectId:  cluster.ProjectId,
		Name:       req.Name,
		BackupType: dataflow.BackupType_MANUAL,
	}
	if req.Description != nil && *req.Description != "" {
		request.Description = *req.Description
	}
	logger := userContext.Logger()
	logger.Info("start to call create manual backup of cluster", zap.Uint64("clusterId", req.GetClusterId()))
	response, err := s.backupConsoleAPI.CreateBackupSchedule(ctx, request)
	logger.Info("end to call create manual backup of cluster", zap.Uint64("clusterId", req.GetClusterId()))
	if err != nil {
		logger.Error("call grpc create manual backup of cluster occur error", zap.Uint64("clusterId", req.GetClusterId()), zap.Error(err))
		return nil, err
	}

	resp := &dp.CreateBackupResp{
		Id: response.BackupId,
	}
	return resp, nil
}

func (s *Service) DeleteBackup(ctx context.Context, req *dp.DeleteBackupReq) (_ *emptypb.Empty, err error) {
	ctx, span := Tracer.Start(ctx, "openapi.backup.DeleteBackup")
	defer tracing.EndSpan(span, err)

	userContext := s.CurrentUser(ctx)

	cluster, err := s.checkDeleteBackup(ctx, userContext, req)
	if err != nil {
		return nil, err
	}

	err = s.callGrpcDeleteBackup(ctx, userContext, req, cluster)
	if err != nil {
		return nil, err
	}
	return &emptypb.Empty{}, nil
}

func (s *Service) callGrpcDeleteBackup(ctx context.Context, userContext *service.UserContext, req *dp.DeleteBackupReq, cluster *core.Cluster) error {
	grpcReq := &dataflow.BackupReq{
		OrgId:     cluster.TenantId,
		ProjectId: cluster.ProjectId,
		ClusterId: cluster.Id,
		Id:        req.BackupId,
	}
	logger := userContext.Logger()
	logger.Info("start to call delete backup of cluster",
		zap.Uint64("clusterId", req.GetClusterId()), zap.Uint64("backupId", req.GetBackupId()))
	_, err := s.backupConsoleAPI.Delete(ctx, grpcReq)
	logger.Info("end to call delete backup of cluster",
		zap.Uint64("clusterId", req.GetClusterId()), zap.Uint64("backupId", req.GetBackupId()))
	if err != nil {
		logger.Error("call delete backup of cluster occur error",
			zap.Uint64("clusterId", req.GetClusterId()), zap.Uint64("backupId", req.GetBackupId()))
		return err
	}
	return nil
}

func (s *Service) checkDeleteBackup(ctx context.Context, userContext *service.UserContext, req *dp.DeleteBackupReq) (_ *core.Cluster, err error) {
	err = userContext.CheckProjectID(entity.ID(req.ProjectId))
	if err != nil {
		return nil, err
	}
	cluster, err := s.checkInfoByClusterID(ctx, userContext, req.GetClusterId())
	if err != nil {
		return nil, err
	}
	return cluster, nil
}

func (s *Service) checkInfoByClusterID(ctx context.Context, userContext *service.UserContext, clusterID uint64) (*core.Cluster, error) {
	cluster, err := s.CheckOrgIDAndProjectIDByClusterID(ctx, userContext, clusterID)
	if err != nil {
		return nil, err
	}
	if cluster.IsDevTier {
		return nil, bizerr.New(bizerr.ErrCodePermission, "Developer Tier does not support Backup.")
	}
	return cluster, nil
}
