package spu

import (
	"context"

	"google.golang.org/protobuf/types/known/emptypb"

	globalcommonv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/common/v1"
	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/utilities/bizerr"
)

func (s *serverImpl) CreateSku(
	ctx context.Context,
	req *tidbmgmtservicev1.CreateSkuRequest,
) (*tidbmgmtservicev1.Sku, error) {
	ctx, span := tracer.Start(ctx, "CreateSku")
	defer span.End()

	if req.GetSku() == nil {
		return nil, bizerr.New(bizerr.ErrCodeParams, "Sku is required")
	}
	sku := req.GetSku()

	outputs, err := s.dedicatedCli.BatchUpsertSkus(ctx, &globaldedicatedv1.BatchUpsertSkusRequest{
		Adds: []*globaldedicatedv1.Sku{covertSkuToGlobal(sku)},
	})
	if err != nil {
		return nil, err
	}
	if len(outputs.GetAdds()) != 1 {
		return nil, bizerr.New(bizerr.ErrCodeGRPC, "unexpected sku added")
	}
	return covertSkuToMgmt(outputs.GetAdds()[0]), nil
}

func (s *serverImpl) UpdateSku(
	ctx context.Context,
	req *tidbmgmtservicev1.UpdateSkuRequest,
) (*tidbmgmtservicev1.Sku, error) {
	ctx, span := tracer.Start(ctx, "UpdateSku")
	defer span.End()

	if req.GetSku() == nil {
		return nil, bizerr.New(bizerr.ErrCodeParams, "Sku is required")
	}
	sku := req.GetSku()
	if sku.GetId() == 0 {
		return nil, bizerr.New(bizerr.ErrCodeParams, "SkuId is required")
	}
	outputs, err := s.dedicatedCli.BatchUpsertSkus(ctx, &globaldedicatedv1.BatchUpsertSkusRequest{
		Updates: []*globaldedicatedv1.BatchUpsertSkusRequest_Update{sku},
	})
	if err != nil {
		return nil, err
	}
	if len(outputs.GetUpdates()) != 1 {
		return nil, bizerr.New(bizerr.ErrCodeGRPC, "unexpected sku updated")
	}
	return covertSkuToMgmt(outputs.GetUpdates()[0]), nil
}

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

	if req.GetId() == 0 {
		return nil, bizerr.New(bizerr.ErrCodeParams, "SkuId is required")
	}
	_, err := s.dedicatedCli.BatchUpsertSkus(ctx, &globaldedicatedv1.BatchUpsertSkusRequest{
		Deletes: []uint64{req.GetId()},
	})
	if err != nil {
		return nil, err
	}
	return &emptypb.Empty{}, nil
}

func (s *serverImpl) GetSku(ctx context.Context, req *tidbmgmtservicev1.GetSkuRequest) (*tidbmgmtservicev1.Sku, error) {
	ctx, span := tracer.Start(ctx, "GetSku")
	defer span.End()

	if req.GetId() == 0 {
		return nil, bizerr.New(bizerr.ErrCodeParams, "SkuId is required")
	}
	output, err := s.dedicatedCli.GetSku(ctx, &globaldedicatedv1.GetSkuRequest{
		Id: req.GetId(),
	})
	if err != nil {
		return nil, err
	}
	return covertSkuToMgmt(output), nil
}

func (s *serverImpl) ListSkus(
	ctx context.Context,
	req *tidbmgmtservicev1.ListSkusRequest,
) (*tidbmgmtservicev1.ListSkusResponse, error) {
	ctx, span := tracer.Start(ctx, "ListSkus")
	defer span.End()

	cloudProviders := []globalcommonv1.CloudProvider_Name{}
	if req.GetCloudProvider() != globalcommonv1.CloudProvider_NAME_UNSPECIFIED {
		cloudProviders = append(cloudProviders, req.GetCloudProvider())
	}

	outputs, err := s.dedicatedCli.ListSkus(ctx, &globaldedicatedv1.ListSkusRequest{
		CloudProviders: cloudProviders,
		ComponentTypes: req.GetComponentTypes(),
	})
	if err != nil {
		return nil, err
	}
	skus := make([]*tidbmgmtservicev1.Sku, 0, len(outputs.Skus))
	for _, output := range outputs.GetSkus() {
		skus = append(skus, covertSkuToMgmt(output))
	}
	return &tidbmgmtservicev1.ListSkusResponse{
		Skus: skus,
	}, nil
}

func covertSkuToGlobal(sku *tidbmgmtservicev1.Sku) *globaldedicatedv1.Sku {
	globalSku := &globaldedicatedv1.Sku{
		Id:               sku.Id,
		SpuId:            sku.SpuId,
		SpuKey:           sku.SpuKey,
		SpuName:          sku.SpuName,
		SpuDisplayName:   sku.SpuDisplayName,
		SpuVersion:       sku.SpuVersion,
		Key:              sku.Key,
		CloudProvider:    sku.CloudProvider,
		ComponentType:    sku.ComponentType,
		InstanceMuId:     sku.InstanceMuId,
		CpuArch:          sku.CpuArch,
		InstanceType:     sku.InstanceType,
		CpuCore:          sku.CpuCore,
		MemoryGi:         sku.MemoryGi,
		DefaultNodeCount: sku.DefaultNodeCount,
		Conditions:       sku.Conditions,
		Priority:         sku.Priority,
		DataStorage:      sku.DataStorage,
		RaftlogStorage:   sku.RaftlogStorage,
	}
	return globalSku
}

func covertSkuToMgmt(globalSku *globaldedicatedv1.Sku) *tidbmgmtservicev1.Sku {
	mgmtSku := &tidbmgmtservicev1.Sku{
		Id:               globalSku.Id,
		SpuId:            globalSku.SpuId,
		SpuKey:           globalSku.SpuKey,
		SpuName:          globalSku.SpuName,
		SpuDisplayName:   globalSku.SpuDisplayName,
		SpuVersion:       globalSku.SpuVersion,
		Key:              globalSku.Key,
		CloudProvider:    globalSku.CloudProvider,
		ComponentType:    globalSku.ComponentType,
		InstanceMuId:     globalSku.InstanceMuId,
		CpuArch:          globalSku.CpuArch,
		InstanceType:     globalSku.InstanceType,
		CpuCore:          globalSku.CpuCore,
		MemoryGi:         globalSku.MemoryGi,
		DefaultNodeCount: globalSku.DefaultNodeCount,
		Conditions:       globalSku.Conditions,
		Priority:         globalSku.Priority,
		DataStorage:      globalSku.DataStorage,
		RaftlogStorage:   globalSku.RaftlogStorage,
	}
	return mgmtSku
}
