package planreplayer

import (
	"context"
	"time"

	"github.com/tidbcloud/pkgv2/db"
	"go.opentelemetry.io/otel"
	"google.golang.org/protobuf/types/known/timestamppb"

	globalcommonv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/common/v1"
	globalngv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/nextgen/v1"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/adapter/eventbus"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/entity"
	clusterrepo "github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/repository/cluster"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
	"github.com/tidbcloud/tidb-management-service/utilities/clusterevent"
)

var tracer = otel.Tracer("domain.planreplayer")

type Service interface {
	GetPlanReplayerStatus(ctx context.Context, clusterID uint64) (*globalngv1.PlanReplayerStatus, bizerr.BizError)
	UpdatePlanReplayerStatus(ctx context.Context, req *globalngv1.UpdatePlanReplayerStatusRequest) (*globalngv1.PlanReplayerStatus, bizerr.BizError)
}

type service struct {
	clusterRepo    clusterrepo.Repo
	transaction    db.Transactioner
	eventCommitter eventbus.WALCommitter
}

func NewService(
	clusterRepo clusterrepo.Repo,
	transaction db.Transactioner,
	eventCommitter eventbus.WALCommitter,
) Service {
	return &service{
		clusterRepo:    clusterRepo,
		transaction:    transaction,
		eventCommitter: eventCommitter,
	}
}

func (s *service) GetPlanReplayerStatus(ctx context.Context, clusterID uint64) (*globalngv1.PlanReplayerStatus, bizerr.BizError) {
	ctx, span := tracer.Start(ctx, "GetPlanReplayerStatus")
	defer span.End()

	if clusterID == 0 {
		return nil, bizerr.New(bizerr.ErrCodeParams, "cluster id is required")
	}

	cluster, err := s.clusterRepo.Get(ctx, clusterID, false)
	if err != nil {
		return nil, err
	}
	if cluster == nil {
		return nil, bizerr.New(bizerr.ErrCodeRecordNotFound, "cluster not found")
	}

	if berr := validateServicePlan(cluster); berr != nil {
		return nil, berr
	}

	return buildStatus(cluster.ClusterId, cluster.PlanReplayerExpireAt), nil
}

func buildStatus(clusterID uint64, expireAt *time.Time) *globalngv1.PlanReplayerStatus {
	status := &globalngv1.PlanReplayerStatus{ClusterId: clusterID}
	if expireAt == nil {
		status.Enabled = false
		return status
	}
	if expireAt.After(time.Now().UTC()) {
		status.Enabled = true
		status.ExpireTime = timestamppb.New(expireAt.UTC())
		return status
	}
	status.Enabled = false
	return status
}

func validateServicePlan(cluster *entity.Cluster) bizerr.BizError {
	switch cluster.GetServicePlan() {
	case globalcommonv1.ServicePlan_Premium,
		globalcommonv1.ServicePlan_Essential_V2,
		globalcommonv1.ServicePlan_Premium_Reserved:
		return nil
	default:
		return bizerr.New(
			bizerr.ErrCodeParams,
			"plan replayer is only supported for premium, essential_v2, and premium_reserved clusters",
		)
	}
}

func validatePatch(patch *globalngv1.PlanReplayerStatusPatch) (time.Time, bool, bizerr.BizError) {
	if patch == nil {
		return time.Time{}, false, bizerr.New(bizerr.ErrCodeParams, "plan_replayer_status is required")
	}

	if !patch.GetEnabled() {
		if patch.GetExpireTime() != nil {
			return time.Time{}, false, bizerr.New(bizerr.ErrCodeParams, "expire_time must be empty when disabling plan replayer")
		}
		return time.Time{}, false, nil
	}

	expireTime := patch.GetExpireTime()
	if expireTime == nil {
		return time.Time{}, false, bizerr.New(bizerr.ErrCodeParams, "expire_time is required when enabling plan replayer")
	}

	expireAt := expireTime.AsTime().UTC()
	if !expireAt.After(time.Now().UTC()) {
		return time.Time{}, false, bizerr.New(bizerr.ErrCodeParams, "expire_time must be a future timestamp")
	}

	return expireAt, true, nil
}

func buildUpdateEvent(
	ctx context.Context,
	cluster *entity.Cluster,
	enabled bool,
	expireAt *time.Time,
) (*clusterevent.ClusterEvent, bizerr.BizError) {
	ev, err := entity.NewClusterEvent(ctx, cluster, clusterevent.EventTypeApplied, "CLUSTER_STATUS_NORMAL")
	if err != nil {
		return nil, err
	}

	if ev.ExtraInfo == nil {
		ev.ExtraInfo = &clusterevent.EventExtraInfo{}
	}
	ev.ExtraInfo.PlanReplayerEnabled = &enabled
	if enabled && expireAt != nil {
		ev.ExtraInfo.PlanReplayerExpireTime = expireAt.UTC().Format(time.RFC3339)
	}

	return ev, nil
}
