package spu

import (
	"context"

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

	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) CreateSpu(
	ctx context.Context,
	req *tidbmgmtservicev1.CreateSpuRequest,
) (*tidbmgmtservicev1.Spu, error) {
	ctx, span := tracer.Start(ctx, "CreateSpu")
	defer span.End()

	if req.GetSpu() == nil {
		return nil, bizerr.New(bizerr.ErrCodeParams, "Spu is required")
	}
	spu := req.GetSpu()

	outputs, err := s.dedicatedCli.BatchUpsertSpus(ctx, &globaldedicatedv1.BatchUpsertSpusRequest{
		Adds: []*globaldedicatedv1.Spu{covertSpuToGlobal(spu)},
	})
	if err != nil {
		return nil, err
	}
	if len(outputs.GetAdds()) != 1 {
		return nil, bizerr.New(bizerr.ErrCodeGRPC, "unexpected spu added")
	}
	return covertSpuToMgmt(outputs.GetAdds()[0]), nil
}

func (s *serverImpl) UpdateSpu(
	ctx context.Context,
	req *tidbmgmtservicev1.UpdateSpuRequest,
) (*tidbmgmtservicev1.Spu, error) {
	ctx, span := tracer.Start(ctx, "UpdateSpu")
	defer span.End()

	if req.GetSpu() == nil {
		return nil, bizerr.New(bizerr.ErrCodeParams, "Spu is required")
	}
	spu := req.GetSpu()
	if spu.GetId() == 0 {
		return nil, bizerr.New(bizerr.ErrCodeParams, "SpuId is required")
	}
	outputs, err := s.dedicatedCli.BatchUpsertSpus(ctx, &globaldedicatedv1.BatchUpsertSpusRequest{
		Updates: []*globaldedicatedv1.BatchUpsertSpusRequest_Update{
			{
				Id:          spu.GetId(),
				DisplayName: spu.GetDisplayName(),
				Mappings:    spu.GetMappings(),
			},
		},
	})
	if err != nil {
		return nil, err
	}
	if len(outputs.GetUpdates()) != 1 {
		return nil, bizerr.New(bizerr.ErrCodeGRPC, "unexpected spu updated")
	}
	return covertSpuToMgmt(outputs.GetUpdates()[0]), nil
}

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

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

func (s *serverImpl) GetSpu(ctx context.Context, req *tidbmgmtservicev1.GetSpuRequest) (*tidbmgmtservicev1.Spu, error) {
	ctx, span := tracer.Start(ctx, "GetSpu")
	defer span.End()

	if req.GetId() == 0 {
		return nil, bizerr.New(bizerr.ErrCodeParams, "SpuId is required")
	}
	output, err := s.dedicatedCli.GetSpu(ctx, &globaldedicatedv1.GetSpuRequest{
		Id: req.GetId(),
	})
	if err != nil {
		return nil, err
	}
	return covertSpuToMgmt(output), nil
}

func (s *serverImpl) ListSpus(
	ctx context.Context,
	req *tidbmgmtservicev1.ListSpusRequest,
) (*tidbmgmtservicev1.ListSpusResponse, error) {
	ctx, span := tracer.Start(ctx, "ListSpus")
	defer span.End()

	outputs, err := s.dedicatedCli.ListSpus(ctx, &globaldedicatedv1.ListSpusRequest{
		Request: &globaldedicatedv1.ListSpusRequest_WithoutSku{
			WithoutSku: &globaldedicatedv1.ListSpusRequest_FilterWithoutSku{
				ComponentTypes: append(req.GetComponentTypes(), req.GetComponentType()),
				Versions:       req.GetVersions(),
			},
		},
	})
	if err != nil {
		return nil, err
	}
	spus := make([]*tidbmgmtservicev1.Spu, 0, len(outputs.Spus))
	for _, spu := range outputs.GetSpus() {
		spus = append(spus, covertSpuToMgmt(spu))
	}
	return &tidbmgmtservicev1.ListSpusResponse{
		Spus: spus,
	}, nil
}

func covertSpuToGlobal(spu *tidbmgmtservicev1.Spu) *globaldedicatedv1.Spu {
	return &globaldedicatedv1.Spu{
		Id:            spu.Id,
		Key:           spu.Key,
		DisplayName:   spu.DisplayName,
		Name:          spu.Name,
		Version:       spu.Version,
		ComponentType: spu.ComponentType,
		CpuCore:       spu.CpuCore,
		MemorySizeGi:  spu.MemorySizeGi,
		Mappings:      spu.Mappings,
	}
}

func covertSpuToMgmt(spu *globaldedicatedv1.Spu) *tidbmgmtservicev1.Spu {
	return &tidbmgmtservicev1.Spu{
		Id:            spu.Id,
		Key:           spu.Key,
		DisplayName:   spu.DisplayName,
		Name:          spu.Name,
		Version:       spu.Version,
		ComponentType: spu.ComponentType,
		CpuCore:       spu.CpuCore,
		MemorySizeGi:  spu.MemorySizeGi,
		Mappings:      spu.Mappings,
	}
}
