package resourcepool

import (
	"context"
	"errors"

	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"

	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/service/resourcepool"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
)

type server struct {
	globalngv1.UnimplementedResourcePoolServiceServer

	resourcePoolSvc resourcepool.Service
}

func NewServer(
	resourcePoolSvc resourcepool.Service,
) globalngv1.ResourcePoolServiceServer {
	return &server{
		resourcePoolSvc: resourcePoolSvc,
	}
}

func (s *server) CreateResourcePool(ctx context.Context,
	request *globalngv1.CreateResourcePoolRequest) (*globalngv1.ResourcePoolProfile, error) {
	if err := validateCreateResourcePoolRequest(request); err != nil {
		return nil, err
	}

	r, err := s.resourcePoolSvc.CreateResourcePool(ctx, request)
	if err != nil {
		var cidrFailure *resourcepool.CIDRValidationFailure
		if errors.As(err, &cidrFailure) {
			if len(cidrFailure.Findings) == 0 {
				return nil, status.Error(codes.Internal, "resource pool CIDR validation detail is empty")
			}
			withDetails, detailErr := status.New(codes.InvalidArgument, "resource pool VPC CIDR is invalid").WithDetails(
				&globalngv1.ResourcePoolCidrValidationError{BlockingFindings: cidrFailure.Findings},
			)
			if detailErr != nil {
				return nil, status.Error(codes.Internal, "failed to encode resource pool CIDR validation detail")
			}
			return nil, withDetails.Err()
		}
		return nil, err
	}
	return r, nil
}

func validateCreateResourcePoolRequest(request *globalngv1.CreateResourcePoolRequest) bizerr.BizError {
	if request == nil || request.GetResourcePool() == nil {
		return nil
	}

	resourcePool := request.GetResourcePool()
	if resourcePool.GetPlacementGroupEnabled() {
		if resourcePool.GetProviderId() != globalcommonv1.CloudProvider_AWS {
			return bizerr.New(
				bizerr.ErrCodeParams,
				"placement_group_enabled requires AWS provider (got %s)", resourcePool.GetProviderId(),
			)
		}
	}

	if resourcePool.GetOwnerType() == globalngv1.ResourcePool_SHARED &&
		resourcePool.GetServicePlan() != globalcommonv1.ServicePlan_Essential_V2 {
		return bizerr.New(
			bizerr.ErrCodeParams,
			"shared resource pool only supports Essential_V2 service plan",
		)
	}

	return nil
}

func (s *server) ListResourcePools(ctx context.Context,
	request *globalngv1.ListResourcePoolsRequest) (*globalngv1.ListResourcePoolsResponse, error) {
	rps, err := s.resourcePoolSvc.ListResourcePools(ctx, request)
	if err != nil {
		return nil, err
	}
	return rps, nil
}

func (s *server) GetResourcePool(ctx context.Context,
	request *globalngv1.GetResourcePoolRequest) (*globalngv1.ResourcePoolProfile, error) {
	rp, err := s.resourcePoolSvc.GetResourcePool(ctx, request)
	if err != nil {
		return nil, err
	}
	return rp, nil
}

func (s *server) UpdateResourcePoolName(ctx context.Context,
	request *globalngv1.UpdateResourcePoolNameRequest) (*globalngv1.ResourcePool, error) {
	rp, err := s.resourcePoolSvc.UpdateResourcePoolName(ctx, request)
	if err != nil {
		return nil, err
	}
	return rp.ResourcePool, nil
}

func (s *server) UpdateResourcePoolInstanceLimit(ctx context.Context,
	request *globalngv1.UpdateResourcePoolInstanceLimitRequest,
) (*globalngv1.ResourcePool, error) {
	rp, err := s.resourcePoolSvc.UpdateResourcePoolInstanceLimit(ctx, request)
	if err != nil {
		return nil, err
	}
	return rp.ResourcePool, nil
}

func (s *server) DeleteResourcePool(ctx context.Context,
	request *globalngv1.DeleteResourcePoolRequest) (*globalngv1.ResourcePool, error) {
	rp, err := s.resourcePoolSvc.DeleteResourcePool(ctx, request)
	if err != nil {
		return nil, err
	}
	return rp.ResourcePool, nil
}

func (s *server) ListOwnerResourcePools(ctx context.Context,
	request *globalngv1.ListOwnerResourcePoolsRequest) (*globalngv1.ListOwnerResourcePoolsResponse, error) {
	resourcePools, strategies, err := s.resourcePoolSvc.ListOwnerResourcePools(ctx, request)
	if err != nil {
		return nil, err
	}

	// group resourcePools and strategies by region and cloud provider
	strategyMap := make(map[string]*globalngv1.ResourcePoolStrategy)
	for _, strategy := range strategies {
		key := strategy.ProviderId.String() + "-" + strategy.ResourcePoolStrategy.RegionId.String() +
			"-" + strategy.ServicePlan.String()
		strategyMap[key] = strategy.ResourcePoolStrategy
	}

	resourcePoolsMap := make(map[string]*globalngv1.ListOwnerResourcePoolsResponse_RegionResourcePools)
	for _, r := range resourcePools {
		key := r.ResourcePool.ProviderId.String() + "-" + r.ResourcePool.RegionId.String() +
			"-" + r.ResourcePool.ServicePlan.String()
		if _, ok := resourcePoolsMap[key]; !ok {
			resourcePoolsMap[key] = &globalngv1.ListOwnerResourcePoolsResponse_RegionResourcePools{
				RegionId:      r.ResourcePool.RegionId,
				ProviderId:    r.ResourcePool.ProviderId,
				ServicePlan:   r.ResourcePool.ServicePlan,
				ResourcePools: []*globalngv1.ResourcePoolProfile{},
				Strategy:      strategyMap[key],
			}
		}
		resourcePoolsMap[key].ResourcePools = append(resourcePoolsMap[key].ResourcePools, r)
	}

	var regions []*globalngv1.ListOwnerResourcePoolsResponse_RegionResourcePools
	for _, v := range resourcePoolsMap {
		regions = append(regions, v)
	}

	return &globalngv1.ListOwnerResourcePoolsResponse{
		RegionResourcePools: regions,
	}, nil
}

func (s *server) UpdateOrgDefaultResourcePool(ctx context.Context,
	request *globalngv1.UpdateOrgDefaultResourcePoolRequest) (*globalngv1.ResourcePoolStrategy, error) {
	strategy, err := s.resourcePoolSvc.UpdateOrgDefaultResourcePool(ctx, request)
	if err != nil {
		return nil, err
	}
	return strategy.ResourcePoolStrategy, nil
}

func (s *server) GetResourcePoolCapacityConstraints(
	ctx context.Context,
	request *globalngv1.GetResourcePoolCapacityConstraintsRequest,
) (*globalngv1.ResourcePoolCapacityConstraints, error) {
	return s.resourcePoolSvc.GetResourcePoolCapacityConstraints(ctx, request)
}

func (s *server) ResizeResourcePoolCapacity(
	ctx context.Context,
	request *globalngv1.ResizeResourcePoolCapacityRequest,
) (*globalngv1.ResizeResourcePoolCapacityResponse, error) {
	return s.resourcePoolSvc.ResizeResourcePoolCapacity(ctx, request)
}

func (s *server) UpsertResourcePoolCloudResourceTags(
	ctx context.Context,
	request *globalngv1.UpsertResourcePoolCloudResourceTagsRequest,
) (*globalngv1.UpsertResourcePoolCloudResourceTagsResponse, error) {
	return s.resourcePoolSvc.UpsertResourcePoolCloudResourceTags(ctx, request)
}

func (s *server) GetResourcePoolPauseResumeOperation(
	ctx context.Context,
	request *globalngv1.GetResourcePoolPauseResumeOperationRequest,
) (*globalngv1.ResourcePoolPauseResumeOperation, error) {
	return s.resourcePoolSvc.GetResourcePoolPauseResumeOperation(ctx, request)
}

func (s *server) PauseResourcePool(
	ctx context.Context,
	request *globalngv1.PauseResourcePoolRequest,
) (*globalngv1.PauseResourcePoolResponse, error) {
	return s.resourcePoolSvc.PauseResourcePool(ctx, request)
}

func (s *server) BeginPauseResourcePoolAdmission(
	ctx context.Context,
	request *globalngv1.BeginPauseResourcePoolAdmissionRequest,
) (*globalngv1.BeginPauseResourcePoolAdmissionResponse, error) {
	return s.resourcePoolSvc.BeginPauseResourcePoolAdmission(ctx, request)
}

func (s *server) ResolvePauseResourcePoolAdmission(
	ctx context.Context,
	request *globalngv1.ResolvePauseResourcePoolAdmissionRequest,
) (*globalngv1.ResolvePauseResourcePoolAdmissionResponse, error) {
	return s.resourcePoolSvc.ResolvePauseResourcePoolAdmission(ctx, request)
}

func (s *server) ResumeResourcePool(
	ctx context.Context,
	request *globalngv1.ResumeResourcePoolRequest,
) (*globalngv1.ResumeResourcePoolResponse, error) {
	return s.resourcePoolSvc.ResumeResourcePool(ctx, request)
}
