package privatelinkservicemigration

import (
	"context"
	"errors"
	"strconv"

	"github.com/samber/lo"
	"go.opentelemetry.io/otel"
	"google.golang.org/protobuf/types/known/emptypb"
	"gorm.io/gorm"

	globaldedicatedv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/dedicated/v1"
	tidbmgmtservicev1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_mgmt_service/v1"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/adapter/dedicatedglobal"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/entity"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/service/dedicated/cluster"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/pkg/apimiddleware/bizerror"
	"github.com/tidbcloud/tidb-management-service/utilities/grpc/metadata"
)

var tracer = otel.Tracer("App.Dedicated.PrivateLinkServiceMigration")

func NewServer(
	dedicatedCli dedicatedglobal.DedicatedGlobal,
	dedicatedClusterSvc cluster.Service,
) tidbmgmtservicev1.PrivateLinkServiceMigrationServiceServer {
	return &serverImpl{
		dedicatedCli:        dedicatedCli,
		dedicatedClusterSvc: dedicatedClusterSvc,
	}
}

type serverImpl struct {
	dedicatedCli        dedicatedglobal.DedicatedGlobal
	dedicatedClusterSvc cluster.Service
}

func (s *serverImpl) CreatePrivateLinkServiceMigration(
	ctx context.Context,
	req *tidbmgmtservicev1.CreatePrivateLinkServiceMigrationRequest,
) (*tidbmgmtservicev1.PrivateLinkServiceMigration, error) {
	ctx, span := tracer.Start(ctx, "CreatePrivateLinkServiceMigration")
	defer span.End()

	tidbgroups := []*globaldedicatedv1.PrivateLinkServiceMigration_MigrationTidbGroup{}
	for _, mtg := range req.GetMigration().GetMigrationTidbGroups() {
		sourceTidbGroupId, err := strconv.ParseUint(mtg.GetSourceTidbGroupId(), 10, 64)
		if err != nil {
			return nil, err
		}
		targetTidbGroupId, err := strconv.ParseUint(mtg.GetTargetTidbGroupId(), 10, 64)
		if err != nil {
			return nil, err
		}
		tidbgroups = append(tidbgroups, &globaldedicatedv1.PrivateLinkServiceMigration_MigrationTidbGroup{
			SourceTidbGroupId: sourceTidbGroupId,
			TargetTidbGroupId: targetTidbGroupId,
		})
	}

	sourceCluster, err := s.dedicatedClusterSvc.GetCluster(
		ctx,
		req.GetMigration().GetSourceClusterId(),
		entity.ClusterViewClusterViewLocal,
		false,
		false,
	)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, bizerror.NewNotFound(err, "dedicated cluster not found")
		}
		return nil, bizerror.NewInternal(err, "failed to get dedicated cluster")
	}

	// gen request meta and append to outgoing meta
	ctx = metadata.AppendToOutgoingMeta(ctx, &metadata.RequestMeta{
		OrgID:             sourceCluster.OrganizationId,
		DefaultProjectID:  sourceCluster.ProjectId,
		ValidProjectList:  []uint64{sourceCluster.ProjectId},
		UserID:            "0",
		IsCheckPermission: lo.ToPtr(true),
	})

	resp, err := s.dedicatedCli.CreatePrivateLinkServiceMigration(ctx, &globaldedicatedv1.CreatePrivateLinkServiceMigrationRequest{
		Migration: &globaldedicatedv1.PrivateLinkServiceMigration{
			SourceClusterId: req.GetMigration().GetSourceClusterId(),
			TargetClusterId: req.GetMigration().GetTargetClusterId(),
			RunMode:         globaldedicatedv1.PrivateLinkServiceMigration_PARALLEL, // only support parallel mode
			MigrationPlan: &globaldedicatedv1.PrivateLinkServiceMigration_MigrationPlan{
				DisableRecreatePrivateLinkServiceForSourceTidbGroup: req.GetMigration().
					GetMigrationPlan().
					GetDisableRecreatePrivateLinkServiceForSourceTidbGroup(),
				TimeoutSeconds: lo.ToPtr(req.GetMigration().GetMigrationPlan().GetTimeoutSeconds()),
			},
			MigrationTidbGroups: tidbgroups,
		},
		ValidateOnly: req.GetValidateOnly(),
	})
	if err != nil {
		return nil, err
	}
	return cvtToMgmtserviceMigration(resp), nil
}

func (s *serverImpl) GetPrivateLinkServiceMigration(
	ctx context.Context,
	req *tidbmgmtservicev1.GetPrivateLinkServiceMigrationRequest,
) (*tidbmgmtservicev1.PrivateLinkServiceMigration, error) {
	ctx, span := tracer.Start(ctx, "GetPrivateLinkServiceMigration")
	defer span.End()

	resp, err := s.dedicatedCli.GetPrivateLinkServiceMigration(ctx, &globaldedicatedv1.GetPrivateLinkServiceMigrationRequest{
		Id: req.GetId(),
	})
	if err != nil {
		return nil, err
	}
	return cvtToMgmtserviceMigration(resp), nil
}

func (s *serverImpl) ListPrivateLinkServiceMigrations(
	ctx context.Context,
	req *tidbmgmtservicev1.ListPrivateLinkServiceMigrationsRequest,
) (*tidbmgmtservicev1.ListPrivateLinkServiceMigrationsResponse, error) {
	ctx, span := tracer.Start(ctx, "ListPrivateLinkServiceMigrations")
	defer span.End()

	resp, err := s.dedicatedCli.ListPrivateLinkServiceMigrations(ctx, &globaldedicatedv1.ListPrivateLinkServiceMigrationsRequest{
		SourceClusterId: req.GetSourceClusterId(),
		TargetClusterId: req.GetTargetClusterId(),
		OrganizationId:  req.GetOrganizationId(),
		ProjectId:       req.GetProjectId(),
	})
	if err != nil {
		return nil, err
	}
	return &tidbmgmtservicev1.ListPrivateLinkServiceMigrationsResponse{
		Migrations: lo.Map(
			resp.Migrations,
			func(migration *globaldedicatedv1.PrivateLinkServiceMigration, _ int) *tidbmgmtservicev1.PrivateLinkServiceMigration {
				return cvtToMgmtserviceMigration(migration)
			},
		),
	}, nil
}

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

	migration, err := s.dedicatedCli.GetPrivateLinkServiceMigration(ctx, &globaldedicatedv1.GetPrivateLinkServiceMigrationRequest{
		Id: req.GetId(),
	})
	if err != nil {
		return nil, err
	}

	sourceCluster, err := s.dedicatedClusterSvc.GetCluster(ctx, migration.SourceClusterId, entity.ClusterViewClusterViewLocal, false, false)
	if err != nil {
		return nil, bizerror.NewInternal(err, "failed to get dedicated cluster")
	}

	// gen request meta and append to outgoing meta
	ctx = metadata.AppendToOutgoingMeta(ctx, &metadata.RequestMeta{
		OrgID:             sourceCluster.OrganizationId,
		DefaultProjectID:  sourceCluster.ProjectId,
		ValidProjectList:  []uint64{sourceCluster.ProjectId},
		UserID:            "0",
		IsCheckPermission: lo.ToPtr(true),
	})

	_, err = s.dedicatedCli.CancelPrivateLinkServiceMigration(ctx, &globaldedicatedv1.CancelPrivateLinkServiceMigrationRequest{
		Id: req.GetId(),
	})
	if err != nil {
		return nil, err
	}
	return &emptypb.Empty{}, nil
}

func cvtToMgmtserviceMigration(migration *globaldedicatedv1.PrivateLinkServiceMigration) *tidbmgmtservicev1.PrivateLinkServiceMigration {
	mtgs := []*tidbmgmtservicev1.PrivateLinkServiceMigration_MigrationTidbGroup{}
	for _, mtg := range migration.MigrationTidbGroups {
		mtgs = append(mtgs, &tidbmgmtservicev1.PrivateLinkServiceMigration_MigrationTidbGroup{
			SourceTidbGroupId: strconv.FormatUint(mtg.SourceTidbGroupId, 10),
			TargetTidbGroupId: strconv.FormatUint(mtg.TargetTidbGroupId, 10),
			State:             tidbmgmtservicev1.PrivateLinkServiceMigration_State(mtg.State),
			Message:           mtg.Message,
			StartTime:         mtg.StartTime,
			CompleteTime:      mtg.CompleteTime,
		})
	}
	return &tidbmgmtservicev1.PrivateLinkServiceMigration{
		Id:              migration.Id,
		SourceClusterId: migration.SourceClusterId,
		TargetClusterId: migration.TargetClusterId,
		RunMode:         tidbmgmtservicev1.PrivateLinkServiceMigration_RunMode(migration.RunMode),
		MigrationPlan: &tidbmgmtservicev1.MigrationPlan{
			DisableRecreatePrivateLinkServiceForSourceTidbGroup: migration.MigrationPlan.DisableRecreatePrivateLinkServiceForSourceTidbGroup,
			TimeoutSeconds: migration.MigrationPlan.TimeoutSeconds,
		},
		MigrationTidbGroups: mtgs,
		State:               tidbmgmtservicev1.PrivateLinkServiceMigration_State(migration.State),
		Message:             migration.Message,
		OrganizationId:      migration.OrganizationId,
		ProjectId:           migration.ProjectId,
		CreateTime:          migration.CreateTime,
		UpdateTime:          migration.UpdateTime,
	}
}
