package backup

import (
	"context"
	"errors"
	"fmt"
	"strconv"

	"github.com/tidbcloud/tiinf/monitor/tilog"
	"github.com/tidbcloud/tiinf/sdk/auditlog"
	"go.uber.org/zap"
	"google.golang.org/protobuf/types/known/emptypb"

	globalngv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/nextgen/v1"
	openapingv1beta2 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_cloud_open_api/nextgen/v1beta2"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/adapter/eventbus"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/entity"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
	"github.com/tidbcloud/tidb-management-service/utilities/grpc/metadata"
)

func (s serverImpl) DeleteBackup(
	ctx context.Context,
	req *openapingv1beta2.DeleteBackupRequest,
) (*emptypb.Empty, error) {
	ctx, span := tracer.Start(ctx, "DeleteBackup")
	defer span.End()

	var (
		err       error
		reqMeta   *metadata.RequestMeta
		theBackup *globalngv1.Backup
	)

	detailsFunc := func() auditlog.Details {
		details := eventbus.NewAuditLogDetails(eventbus.TierTypePremium)
		details["backup_id"] = req.BackupId
		if theBackup != nil {
			details["backup_name"] = theBackup.GetDisplayName()
			details["trigger_type"] = openapingv1beta2.Backup_TriggerType(theBackup.TriggerType).String()
			details["tidb_id"] = theBackup.GetClusterId()
			clusterID, _ := strconv.ParseUint(theBackup.GetClusterId(), 10, 64)
			reqMeta.ClusterID = clusterID
		}
		return details
	}
	defer func() {
		if r := recover(); r != nil {
			err = errors.New("internal error")
			tilog.Error(ctx, "main logic panic", zap.Any("panic", r))
		}
		s.auditLogSender.SubmitAuditLogForNextgen(ctx, auditlog.EventDeleteBackup, reqMeta, detailsFunc, err)
	}()

	reqMeta, err = metadata.ExtractRequestMetaWithCheckPermission(ctx)
	if err != nil {
		err = bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "failed to extract request metadata")
		return nil, err
	}

	theBackup, err = s.validateDeleteBackupRequest(ctx, req, reqMeta)
	if err != nil {
		return nil, err
	}

	_, err = s.nextGenCli.DeleteBackup(ctx, &globalngv1.DeleteBackupRequest{
		BackupId:       req.BackupId,
		OrganizationId: strconv.FormatUint(reqMeta.OrgID, 10),
	})
	if err != nil {
		return nil, err
	}

	return &emptypb.Empty{}, nil
}

func (s serverImpl) validateDeleteBackupRequest(
	ctx context.Context,
	req *openapingv1beta2.DeleteBackupRequest,
	reqMeta *metadata.RequestMeta,
) (*globalngv1.Backup, error) {
	resp, err := s.nextGenCli.ListBackups(ctx, &globalngv1.ListBackupsRequest{
		OrganizationIds: []string{fmt.Sprintf("%d", reqMeta.OrgID)},
		BackupIds:       []string{req.BackupId},
	})
	if err != nil {
		return nil, bizerr.WrapError(err, "failed to list backups")
	}

	if len(resp.Backups) == 0 {
		return nil, bizerr.New(bizerr.ErrCodeParams, "backup not found")
	}

	if len(resp.Backups) != 1 {
		return nil, bizerr.New(bizerr.GeneralInternalErrorCode, "more than one backup found")
	}

	clusterID, err := strconv.ParseUint(resp.Backups[0].GetClusterId(), 10, 64)
	if err != nil {
		return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "invalid tidb id: %s", resp.Backups[0].GetClusterId())
	}
	if err := s.checkBackupDeleteRBAC(ctx, reqMeta, clusterID); err != nil {
		return nil, err
	}

	return resp.Backups[0], nil
}

func (s serverImpl) checkBackupDeleteRBAC(
	ctx context.Context,
	reqMeta *metadata.RequestMeta,
	clusterID uint64,
) error {
	cluster, err := s.clusterSvc.GetCluster(ctx, clusterID, entity.ClusterViewClusterViewLocal, true)
	if err != nil {
		return bizerr.WrapError(err, "failed to get tidb")
	}
	if cluster == nil || cluster.OrganizationId != reqMeta.OrgID {
		return bizerr.New(bizerr.ErrCodePermission, bizerr.ErrMsgResourceNoPermission)
	}

	allowed, err := s.accountCli.CheckBackupMutationPermission(ctx, reqMeta.OrgID, cluster.ProjectId, clusterID, reqMeta.UserID)
	if err != nil {
		return bizerr.WrapErrorWithCode(err, bizerr.ErrCodePermission, "failed to check backup delete permission")
	}
	if !allowed {
		return bizerr.New(bizerr.ErrCodePermission, bizerr.ErrMsgResourceNoPermission)
	}

	return nil
}
