package console

import (
	"context"
	"fmt"
	"path/filepath"
	"strconv"
	"time"

	"github.com/tidbcloud/pkgv2/bizerr"
	"github.com/tidbcloud/pkgv2/log"
	"github.com/tidbcloud/pkgv2/tracing"
	"github.com/tidbcloud/serverless-service/idl/pbgen/proto/console"
	"github.com/tidbcloud/serverless-service/internal/domain/constant"
	"github.com/tidbcloud/serverless-service/internal/domain/entity"
	"github.com/tidbcloud/serverless-service/internal/errorcode"
	stashing "github.com/tidbcloud/serverless-service/internal/infra/stashing_area"
	tidbmgmtservicev1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_mgmt_service/v1"
	"go.opentelemetry.io/otel/attribute"
	"go.uber.org/zap"
)

func (s *importService) Upload(ctx context.Context, req *console.UploadRequest) (_ *console.UploadResponse, err error) {
	ctx, span := Tracer.Start(ctx, "importService.StartUpload")
	defer func() {
		tracing.EndSpan(span, err)
		err = errorcode.PrepareImportErrorMessage(ctx, err)
	}()
	span.SetAttributes(
		attribute.String("cluster_id", req.ClusterId),
		attribute.String("file_name", req.FileName),
	)
	logger := log.WithContextL(ctx).With(zap.String("cluster_id", req.ClusterId), zap.String("file_name", req.FileName))

	if req.FileName == "" {
		return nil, bizerr.New(bizerr.ErrCodeParams, "file name is required")
	}

	clusterID, err := strconv.ParseUint(req.ClusterId, 10, 64)
	if err != nil {
		return nil, bizerr.New(bizerr.ErrCodeParams, "invalid cluster id")
	}
	cluster, err := s.mgmtSvc.GetCluster(ctx, clusterID, tidbmgmtservicev1.ClusterFilter_CLUSTER_VIEW_LOCAL)
	if err != nil {
		logger.Error("failed to get cluster by id", zap.Error(err))
		return nil, err
	}
	stashingArea, err := s.stashingAreaProvider.GetStashingArea(cluster.CloudProvider, cluster.GetRegion())
	if err != nil {
		logger.Error("failed to get stashing area", zap.Error(err))
		return nil, bizerr.New(bizerr.ErrCodeUnknown, "failed to get stashing area")
	}

	id := entity.GenerateIDWithPrefix(constant.UploadIDPrefix)
	key := fmt.Sprintf(
		"%s/%s",
		stashingArea.GetObjectPath(strconv.FormatUint(cluster.OrganizationId, 10), stashing.Import, id),
		filepath.Base(req.FileName),
	)

	request, err := stashingArea.PresignPutObject(ctx, key, 6*time.Hour)
	if err != nil {
		logger.Error("failed to presign put object", zap.Error(err))
		return nil, bizerr.New(bizerr.ErrCodeUnknown, "failed to presign put object")
	}
	bizErr := s.uploadRepo.Create(ctx, &entity.Upload{
		ID:            id,
		Status:        constant.UploadStatusCompleted,
		Multipart:     false,
		AwsUploadID:   "",
		BucketKey:     key,
		TenantID:      cluster.OrganizationId,
		ClusterID:     clusterID,
		CloudProvider: cluster.CloudProvider,
		RegionName:    cluster.Region,
	})
	if bizErr != nil {
		logger.Error("failed to create upload", zap.Error(bizErr))
		return nil, bizErr
	}
	u, bizErr := s.uploadRepo.GetUploadByID(ctx, id)
	if bizErr != nil {
		logger.Error("failed to get upload", zap.Error(bizErr))
		return nil, bizErr
	}

	logger.Info("generate upload url successfully", zap.String("upload_id", id))
	return &console.UploadResponse{
		UploadUrl:    request.URL,
		UploadId:     id,
		CompatibleId: u.IDCompat,
	}, nil
}
