package regionserver

import (
	"context"
	"fmt"
	"net/http"
	"strings"

	"github.com/go-resty/resty/v2"
	"github.com/samber/lo"
	regionaldedicatedv1 "github.com/tidbcloud/infra-provider/api/tidbcloud/regional-server/v1"
	"google.golang.org/protobuf/types/known/emptypb"

	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/adapter/regionserver"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/entity"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
	"github.com/tidbcloud/tidb-management-service/utilities/grpc/metadata"
)

const (
	connectionNotFoundCode = 3001
)

func (s *regionServerCliImpl) CreatePrivateLinkService(
	ctx context.Context,
	region *entity.Region,
	req *regionaldedicatedv1.CreatePrivateLinkServiceRequest,
) (*regionaldedicatedv1.PrivateLinkService, bizerr.BizError) {
	body, err := marshalOptions.Marshal(req.PrivateLinkService)
	if err != nil {
		return nil, bizerr.New(bizerr.ErrCodeJsonMarshal, "%s", err.Error())
	}
	resp, berr := s.callRegion(
		ctx,
		region,
		http.MethodPost,
		operationMap["CreatePrivateLinkService"],
		func(request *resty.Request) *resty.Request {
			request.SetPathParam(clusterIdParameter, req.PrivateLinkService.ClusterId)
			request.SetBody(body)
			return request
		},
	)
	if berr != nil {
		return nil, berr
	}

	var out regionaldedicatedv1.PrivateLinkService
	if err := unmarshalOptions.Unmarshal(resp.Body(), &out); err != nil {
		return nil, bizerr.New(bizerr.ErrCodeRegionalAPI, "%s", err.Error())
	}

	return &out, nil
}

func (s *regionServerCliImpl) GetPrivateLinkService(
	ctx context.Context,
	region *entity.Region,
	req *regionserver.GetPrivateLinkServiceRequest,
) (*regionaldedicatedv1.PrivateLinkService, bizerr.BizError) {
	listPrivateLinkServiceRequest := &regionaldedicatedv1.ListPrivateLinkServicesRequest{
		ClusterId:   req.ClusterId,
		TidbGroupId: req.TidbGroupId,
	}
	listPrivateLinkServiceResponse, err := s.ListPrivateLinkServices(ctx, region, listPrivateLinkServiceRequest)
	if err != nil {
		return nil, err
	}
	if len(listPrivateLinkServiceResponse.PrivateLinkServices) == 0 {
		return nil, bizerr.New(bizerr.ErrCodeRecordNotFound, "private link service not found")
	}
	targets := lo.Filter(listPrivateLinkServiceResponse.PrivateLinkServices, func(service *regionaldedicatedv1.PrivateLinkService, _ int) bool {
		return service.Status != regionaldedicatedv1.PrivateLinkService_DELETING
	})
	if len(targets) == 0 {
		return nil, bizerr.New(bizerr.ErrCodeRecordNotFound, "private link service not found")
	}
	if len(targets) > 1 {
		return nil, bizerr.New(bizerr.ErrCodeUnknown, "multiple private link services found")
	}
	return targets[0], nil
}

func (s *regionServerCliImpl) ListPrivateLinkServices(
	ctx context.Context,
	region *entity.Region,
	req *regionaldedicatedv1.ListPrivateLinkServicesRequest,
) (*regionaldedicatedv1.ListPrivateLinkServicesResponse, bizerr.BizError) {
	resp, err := s.callRegion(
		ctx,
		region,
		http.MethodGet,
		operationMap["ListPrivateLinkServices"],
		func(request *resty.Request) *resty.Request {
			params := map[string]string{
				clusterIdParameter:   req.GetClusterId(),
				tidbGroupIdParameter: req.GetTidbGroupId(),
			}
			request.SetQueryParams(params)
			request.SetPathParam(clusterIdParameter, req.ClusterId)
			return request
		},
	)
	if err != nil {
		return nil, err
	}

	var out regionaldedicatedv1.ListPrivateLinkServicesResponse
	if err := unmarshalOptions.Unmarshal(resp.Body(), &out); err != nil {
		return nil, bizerr.New(bizerr.ErrCodeRegionalAPI, "%s", err.Error())
	}

	return &out, nil
}

func (s *regionServerCliImpl) UpdatePrivateLinkService(
	ctx context.Context,
	region *entity.Region,
	req *regionserver.UpdatePrivateLinkServiceRequest,
) (*regionaldedicatedv1.PrivateLinkService, bizerr.BizError) {
	privateLinkService, bizErr := s.GetPrivateLinkService(
		ctx,
		region,
		&regionserver.GetPrivateLinkServiceRequest{
			ClusterId:   req.ClusterId,
			TidbGroupId: req.TidbGroupId,
		},
	)
	if bizErr != nil {
		return nil, bizErr
	}
	actualRequest := &metadata.ExpectRequest[string]{
		TenantId:   "", // leave it empty
		ProjectId:  "", // leave it empty
		ClusterId:  privateLinkService.ClusterId,
		ResourceId: "", // leave it empty, because Add|Delete private link connection action calls UpdatePrivateLinkService API, private link connection id is not same as private link service id
	}
	if err := metadata.Validate(ctx, actualRequest); err != nil {
		return nil, bizerr.WrapError(err, "validate failed")
	}

	updatePrivateLinkServiceRequest := &regionaldedicatedv1.UpdatePrivateLinkServiceRequest{
		ClusterId:              req.ClusterId,
		PrivateLinkServiceId:   privateLinkService.Id,
		PrivateLinkConnections: req.PrivateLinkConnections,
		ValidateOnly:           req.ValidateOnly,
	}

	body, err := marshalOptions.Marshal(updatePrivateLinkServiceRequest)
	if err != nil {
		return nil, bizerr.New(bizerr.ErrCodeJsonMarshal, "%s", err.Error())
	}
	resp, berr := s.callRegion(
		ctx,
		region,
		http.MethodPatch,
		operationMap["UpdatePrivateLinkService"],
		func(request *resty.Request) *resty.Request {
			request.SetPathParam(clusterIdParameter, req.ClusterId)
			request.SetPathParam(privateLinkServiceIdParameter, privateLinkService.Id)
			request.SetBody(body)
			return request
		},
	)
	if berr != nil {
		if berr.ErrCode() == bizerr.ErrCodeParams && req.ValidateOnly {
			if strings.HasPrefix(berr.ErrMsg(), fmt.Sprintf("[%d]", connectionNotFoundCode)) {
				return nil, berr.OverwriteMessage("your private endpoint can't be found. Please check it and try again")
			}
		}
		return nil, berr
	}

	var out regionaldedicatedv1.PrivateLinkService
	if err := unmarshalOptions.Unmarshal(resp.Body(), &out); err != nil {
		return nil, bizerr.New(bizerr.ErrCodeRegionalAPI, "%s", err.Error())
	}

	return &out, nil
}

func (s *regionServerCliImpl) DeletePrivateLinkService(
	ctx context.Context,
	region *entity.Region,
	req *regionserver.DeletePrivateLinkServiceRequest,
) bizerr.BizError {
	services, err := s.ListPrivateLinkServices(ctx, region, &regionaldedicatedv1.ListPrivateLinkServicesRequest{
		ClusterId:   req.ClusterId,
		TidbGroupId: req.TidbGroupId,
	})
	if err != nil {
		return err
	}
	if len(services.PrivateLinkServices) == 0 {
		return bizerr.New(bizerr.ErrCodeRecordNotFound, "private link service not found")
	}

	var notfound bizerr.BizError
	for _, service := range services.PrivateLinkServices {
		actualRequest := &metadata.ExpectRequest[string]{
			TenantId:   "", // leave it empty
			ProjectId:  "", // leave it empty
			ClusterId:  service.ClusterId,
			ResourceId: service.Id,
		}
		if err := metadata.Validate(ctx, actualRequest); err != nil {
			return bizerr.WrapError(err, "validate failed")
		}

		_, err := s.callRegion(
			ctx,
			region,
			http.MethodDelete,
			operationMap["DeletePrivateLinkService"],
			func(request *resty.Request) *resty.Request {
				request.SetPathParam(clusterIdParameter, req.ClusterId)
				request.SetPathParam(privateLinkServiceIdParameter, service.Id)
				return request
			},
		)
		if err != nil {
			if err.ErrCode() == bizerr.ErrCodeRecordNotFound {
				notfound = err
				continue
			}
			return err
		}
	}
	if notfound != nil {
		return notfound
	}

	return nil
}

func (s *regionServerCliImpl) RejectVpcEndpointConnections(
	ctx context.Context,
	region *entity.Region,
	req *regionserver.RejectVpcEndpointConnectionsRequest,
) (*emptypb.Empty, bizerr.BizError) {
	privateLinkService, bizErr := s.GetPrivateLinkService(
		ctx,
		region,
		&regionserver.GetPrivateLinkServiceRequest{
			ClusterId:   req.ClusterId,
			TidbGroupId: req.TidbGroupId,
		},
	)
	if bizErr != nil {
		return nil, bizErr
	}

	regionalReq := &regionaldedicatedv1.RejectVpcEndpointConnectionsRequest{
		ClusterId: req.ClusterId,
	}
	body, err := marshalOptions.Marshal(regionalReq)
	if err != nil {
		return nil, bizerr.New(bizerr.ErrCodeJsonMarshal, "%s", err.Error())
	}
	_, berr := s.callRegion(
		ctx,
		region,
		http.MethodPost,
		operationMap["RejectVpcEndpointConnections"],
		func(request *resty.Request) *resty.Request {
			request.SetPathParam(clusterIdParameter, req.ClusterId)
			request.SetPathParam(privateLinkServiceIdParameter, privateLinkService.Id)
			request.SetBody(body)
			return request
		},
	)
	if berr != nil {
		return nil, berr
	}

	return &emptypb.Empty{}, nil
}

func (s *regionServerCliImpl) PausePrivateLinkService(
	ctx context.Context,
	region *entity.Region,
	req *regionserver.PausePrivateLinkServiceRequest,
) (*emptypb.Empty, bizerr.BizError) {
	privateLinkService, bizErr := s.GetPrivateLinkService(
		ctx,
		region,
		&regionserver.GetPrivateLinkServiceRequest{
			ClusterId:   req.ClusterId,
			TidbGroupId: req.TidbGroupId,
		},
	)
	if bizErr != nil {
		return nil, bizErr
	}

	regionalReq := &regionaldedicatedv1.PausePrivateLinkServiceRequest{
		ClusterId: req.ClusterId,
	}
	body, err := marshalOptions.Marshal(regionalReq)
	if err != nil {
		return nil, bizerr.New(bizerr.ErrCodeJsonMarshal, "%s", err.Error())
	}
	_, berr := s.callRegion(
		ctx,
		region,
		http.MethodPost,
		operationMap["PausePrivateLinkService"],
		func(request *resty.Request) *resty.Request {
			request.SetPathParam(clusterIdParameter, req.ClusterId)
			request.SetPathParam(privateLinkServiceIdParameter, privateLinkService.Id)
			request.SetBody(body)
			return request
		},
	)
	if berr != nil {
		return nil, berr
	}

	return &emptypb.Empty{}, nil
}

func (s *regionServerCliImpl) PurgePrivateLinkMetadata(
	ctx context.Context,
	region *entity.Region,
	req *regionserver.PurgePrivateLinkMetadataRequest,
) (*emptypb.Empty, bizerr.BizError) {
	privateLinkService, bizErr := s.GetPrivateLinkService(
		ctx,
		region,
		&regionserver.GetPrivateLinkServiceRequest{
			ClusterId:   req.ClusterId,
			TidbGroupId: req.TidbGroupId,
		},
	)
	if bizErr != nil {
		return nil, bizErr
	}

	regionalReq := &regionaldedicatedv1.PurgePrivateLinkMetadataRequest{
		ClusterId: req.ClusterId,
	}
	body, err := marshalOptions.Marshal(regionalReq)
	if err != nil {
		return nil, bizerr.New(bizerr.ErrCodeJsonMarshal, "%s", err.Error())
	}
	_, berr := s.callRegion(
		ctx,
		region,
		http.MethodPost,
		operationMap["PurgePrivateLinkMetadata"],
		func(request *resty.Request) *resty.Request {
			request.SetPathParam(clusterIdParameter, req.ClusterId)
			request.SetPathParam(privateLinkServiceIdParameter, privateLinkService.Id)
			request.SetBody(body)
			return request
		},
	)
	if berr != nil {
		return nil, berr
	}

	return &emptypb.Empty{}, nil
}
