package export

import (
	"context"
	"fmt"
	"strconv"
	"time"

	eventdata "github.com/tidbcloud/eventbus-datastruct/event"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"

	globalngv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/nextgen/v1"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/entity"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/service/displayevent"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/service/workflow/common"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/infra/repoimpl/schema"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
	grpcmetadata "github.com/tidbcloud/tidb-management-service/utilities/grpc/metadata"
	wf "github.com/tidbcloud/tidb-management-service/utilities/workflow"
)

func (s *serviceImpl) CreateExport(ctx context.Context, req *globalngv1.CreateExportRequest) (*globalngv1.Export, bizerr.BizError) {
	if req.ClusterId == "" || req.Export == nil {
		return nil, bizerr.New(bizerr.ErrCodeParams, "cluster_id and export are required")
	}

	tilog.Info(ctx, "create export request", zap.Any("options", req.Export.GetExportOptions()))

	// check cluster
	clusterId, err1 := strconv.ParseUint(req.GetClusterId(), 10, 64)
	if err1 != nil {
		return nil, bizerr.New(bizerr.ErrCodeParams, "invalid cluster_id")
	}
	clusterEnt, err := s.clusterRepo.Get(ctx, clusterId, true)
	if err != nil {
		tilog.Error(ctx, "failed to get cluster", zap.Error(err))
		return nil, err
	}
	if clusterEnt.State != globalngv1.Cluster_AVAILABLE {
		return nil, bizerr.New(bizerr.ErrCodeParams, "cluster is not available for export: state=%s", clusterEnt.State.String())
	}
	if lifecycleErr := s.checkResourcePoolLifecycle(ctx, clusterId, false); lifecycleErr != nil {
		return nil, lifecycleErr
	}

	// validate export
	if err := s.validateCreateExport(ctx, req, clusterEnt.GetCloudProviderId()); err != nil {
		tilog.Error(ctx, "failed to validate export", zap.Error(err))
		return nil, err
	}

	if req.ValidateOnly {
		return req.Export, nil
	}

	// to entity
	schemaExport := protoToSchema(req.Export)
	tidbGroupId := strconv.FormatUint(s.idGeneratorAdapter.MustNew(), 10)
	s.fillInExport(schemaExport, clusterEnt, tidbGroupId)
	metadataJSON, metadataErr := grpcmetadata.MarshalRequestMetaFromCtx(ctx)
	if metadataErr != nil {
		return nil, bizerr.WrapError(metadataErr, "failed to marshal metadata from context")
	}

	errx := s.transaction.Transaction(ctx, func(ctx context.Context) error {
		if lifecycleErr := s.checkResourcePoolLifecycle(ctx, clusterId, true); lifecycleErr != nil {
			return lifecycleErr
		}
		if err := s.exportRepo.Create(ctx, &entity.Export{Export: schemaExport}); err != nil {
			return err
		}
		workflowErr := s.workflow.RunDagWithBizId(
			ctx,
			common.CreateExport,
			strconv.FormatUint(clusterEnt.OrganizationId, 10),
			"0",
			schemaExport.ExportId,
			map[string]string{
				wf.KeyTraceId:          wf.GetTraceId(ctx),
				wf.KeyRequestMeta:      string(metadataJSON),
				common.KeyClusterId:    req.GetClusterId(),
				common.KeyExportId:     schemaExport.ExportId,
				common.KeyTidbGroupId:  tidbGroupId,
				common.KeyTidbGroupIds: tidbGroupId,
			},
		)
		if workflowErr != nil {
			return bizerr.WrapErrorWithCode(workflowErr, bizerr.ErrCodeUnknown, "failed to start export workflow")
		}
		displayEv := newExportDisplayEvent(
			ctx,
			&entity.Export{Export: schemaExport},
			displayevent.CreateExport,
			eventdata.EventStatusStarted,
			"Export started.",
		)
		displayevent.Write(ctx, s.eventCommitter, displayEv)
		return nil
	})
	if errx != nil {
		tilog.Error(ctx, "create export failed", zap.Error(errx), zap.String("export_id", schemaExport.ExportId))
		return nil, bizerr.WrapError(errx, "create export failed")
	}

	return schemaToProto(schemaExport), nil
}

// fillInExport fills everything that request cannot provide
func (s *serviceImpl) fillInExport(exp *schema.Export, cluster *entity.Cluster, tidbGroupId string) {
	exp.ClusterId = strconv.FormatUint(cluster.ClusterId, 10)
	exp.OrganizationId = cluster.OrganizationId
	exp.RegionId = cluster.RegionId
	exp.TidbGroupId = tidbGroupId

	exp.State = schema.ExportStateRunning
	// Use user-provided export_id if available, otherwise generate a new one
	if exp.ExportId == "" {
		exp.ExportId = strconv.FormatUint(s.idGeneratorAdapter.MustNew(), 10)
	}
	// user provided name first
	if exp.DisplayName == "" {
		exp.DisplayName = fmt.Sprintf("SNAPSHOT_%s", time.Now().UTC().Format(time.RFC3339))
	}
}
