package vpc

import (
	"context"
	"fmt"
	"strconv"

	"github.com/tidbcloud/tiinf/sdk/auditlog"
	"google.golang.org/protobuf/types/known/emptypb"

	globaldedicatedv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/dedicated/v1"
	openapicommonv1beta1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_cloud_open_api/common/v1beta1"
	openapidedicatedv1beta1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_cloud_open_api/dedicated/v1beta1"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/adapter/eventbus"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/pkg/apimiddleware/bizerror"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/pkg/common"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
	"github.com/tidbcloud/tidb-management-service/utilities/enum"
	"github.com/tidbcloud/tidb-management-service/utilities/grpc/metadata"
)

const serverGeneratedId = "[server-generated-id]"

func (s *serverImpl) CreateNetworkContainer(
	ctx context.Context,
	req *openapidedicatedv1beta1.CreateNetworkContainerRequest,
) (*openapidedicatedv1beta1.NetworkContainer, error) {
	ctx, span := tracer.Start(ctx, "CreateNetworkContainer")
	defer span.End()

	var (
		err     error
		reqMeta *metadata.RequestMeta
	)

	detailsFunc := func() auditlog.Details {
		details := eventbus.NewAuditLogDetails(eventbus.TierTypeDedicated)
		details[auditlog.KeyRegion] = req.GetNetworkContainer().GetRegionId()
		details[auditlog.KeyCloudProvider] = req.GetNetworkContainer().GetCloudProvider()
		details[auditlog.KeyRegionCIDR] = req.GetNetworkContainer().GetCidrNotation()
		return details
	}
	defer func() {
		s.auditLogSender.SubmitAuditLog(ctx, auditlog.EventCreateProjectCIDR, reqMeta, detailsFunc, err)
	}()

	// Extract request meta.
	reqMeta, err = metadata.ExtractRequestMetaWithCheckPermission(ctx)
	if err != nil {
		return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "failed to extract request metadata")
	}

	var provider, region string
	provider, region, err = common.ParseOpenApiRegionNameOrId(req.GetNetworkContainer().GetRegionId())
	if err != nil {
		return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "invalid region id")
	}

	projectId := reqMeta.DefaultProjectID
	projectIdStr, ok := req.GetNetworkContainer().GetLabels()[common.ProjectIDLabelKey]
	if ok {
		projectId, err = strconv.ParseUint(projectIdStr, 10, 64)
		if err != nil {
			return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "invalid project id")
		}
	}

	resp, err := s.dedicatedCli.CreateCidr(ctx, &globaldedicatedv1.CreateCidrRequest{
		ProjectId:         projectId,
		Cidr:              req.GetNetworkContainer().GetCidrNotation(),
		CloudProviderName: provider,
		RegionName:        region,
		ValidateOnly:      req.GetValidateOnly(),
	})
	if err != nil {
		return nil, err
	}

	// Early return if only validate.
	if req.GetValidateOnly() {
		p, err := enum.FromString[openapicommonv1beta1.Region_CloudProvider](provider)
		if err != nil {
			return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "failed to convert cloud provider")
		}
		return &openapidedicatedv1beta1.NetworkContainer{
			Name:               fmt.Sprintf("networkContainers/%s", serverGeneratedId),
			NetworkContainerId: serverGeneratedId,
			Labels:             map[string]string{common.ProjectIDLabelKey: strconv.FormatUint(projectId, 10)},
			RegionId:           req.GetNetworkContainer().GetRegionId(),
			CidrNotation:       req.GetNetworkContainer().GetCidrNotation(),
			CloudProvider:      openapicommonv1beta1.Region_CloudProvider(p),
			State:              openapidedicatedv1beta1.NetworkContainer_INACTIVE,
		}, nil
	}

	// Get the created cidr.
	var globalCidrResp *globaldedicatedv1.QueryCidrsResponse
	globalCidrResp, err = s.dedicatedCli.QueryCidrs(ctx, &globaldedicatedv1.QueryCidrsRequest{
		Filter: &globaldedicatedv1.QueryCidrsRequest_QueryCidrsFilter{
			ProjectIds:        []uint64{projectId},
			CloudProviderName: provider,
			Ids:               []uint64{resp.GetId()},
		},
	})
	if err != nil {
		return nil, bizerror.NewInternal(err, "failed to query created cidr")
	}
	if len(globalCidrResp.GetItems()) != 1 {
		err_ := fmt.Errorf("unexpected number of cidrs returned: %d", len(globalCidrResp.GetItems()))
		return nil, bizerror.NewInternal(err_, err_.Error())
	}

	var openApiNetworkContainer *openapidedicatedv1beta1.NetworkContainer
	openApiNetworkContainer, err = networkContainerGlobalToOpenApi(globalCidrResp.GetItems()[0])
	if err != nil {
		return nil, bizerror.NewInternal(err, "failed to convert global cidr to open api network container")
	}

	return openApiNetworkContainer, nil
}

func (s *serverImpl) GetNetworkContainer(
	ctx context.Context,
	req *openapidedicatedv1beta1.GetNetworkContainerRequest,
) (*openapidedicatedv1beta1.NetworkContainer, error) {
	ctx, span := tracer.Start(ctx, "GetNetworkContainer")
	defer span.End()

	// Extract request meta.
	_, err := metadata.ExtractRequestMetaWithCheckPermission(ctx)
	if err != nil {
		return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "failed to extract request metadata")
	}

	id, err := strconv.ParseUint(req.GetNetworkContainerId(), 10, 64)
	if err != nil {
		return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "invalid network container id")
	}

	// Get the created cidr.
	globalCidrResp, err := s.dedicatedCli.QueryCidrs(ctx, &globaldedicatedv1.QueryCidrsRequest{
		Filter: &globaldedicatedv1.QueryCidrsRequest_QueryCidrsFilter{
			Ids: []uint64{id},
		},
	})
	if err != nil {
		return nil, bizerror.NewInternal(err, "failed to query created cidr")
	}
	if len(globalCidrResp.GetItems()) > 1 {
		err_ := fmt.Errorf("unexpected number of cidrs returned: %d", len(globalCidrResp.GetItems()))
		return nil, bizerror.NewInternal(err_, err_.Error())
	}
	if len(globalCidrResp.GetItems()) == 0 {
		err_ := fmt.Errorf("cidr not found")
		return nil, bizerror.NewNotFound(err_, err_.Error())
	}

	openApiNetworkContainer, err := networkContainerGlobalToOpenApi(globalCidrResp.GetItems()[0])
	if err != nil {
		return nil, bizerror.NewInternal(err, "failed to convert global cidr to open api network container")
	}

	return openApiNetworkContainer, nil
}

func (s *serverImpl) ListNetworkContainers(
	ctx context.Context,
	req *openapidedicatedv1beta1.ListNetworkContainersRequest,
) (*openapidedicatedv1beta1.ListNetworkContainersResponse, error) {
	ctx, span := tracer.Start(ctx, "ListNetworkContainers")
	defer span.End()

	// Extract request meta.
	reqMeta, err := metadata.ExtractRequestMetaWithCheckPermission(ctx)
	if err != nil {
		return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "failed to extract request metadata")
	}

	projectId := reqMeta.DefaultProjectID
	if req.GetProjectId() != "" {
		projectId, err = strconv.ParseUint(req.GetProjectId(), 10, 64)
		if err != nil {
			return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "invalid project id")
		}
	}

	globalReq := &globaldedicatedv1.QueryCidrsRequest{
		Filter: &globaldedicatedv1.QueryCidrsRequest_QueryCidrsFilter{
			ProjectIds: []uint64{projectId},
		},
	}

	if req.GetCloudProvider() != openapicommonv1beta1.Region_CLOUD_PROVIDER_UNSPECIFIED {
		globalReq.Filter.CloudProviderName = req.GetCloudProvider().String()
	}

	// Get the created cidr.
	globalCidrResp, err := s.dedicatedCli.QueryCidrs(ctx, globalReq)
	if err != nil {
		return nil, bizerror.NewInternal(err, "failed to query created cidr")
	}

	openApiNetworkContainers := make([]*openapidedicatedv1beta1.NetworkContainer, 0, len(globalCidrResp.GetItems()))
	for _, gCidr := range globalCidrResp.GetItems() {
		openApiNetworkContainer, err := networkContainerGlobalToOpenApi(gCidr)
		if err != nil {
			return nil, bizerror.NewInternal(err, "failed to convert global cidr to open api network container")
		}
		openApiNetworkContainers = append(openApiNetworkContainers, openApiNetworkContainer)
	}

	pagedContainers, nextToken, err := common.LocalPagination(
		openApiNetworkContainers,
		req,
		common.ListDefaultPageSize,
		common.ListMaxPageSize,
	)
	if err != nil {
		return nil, bizerror.NewInternal(err, "failed to paginate network containers")
	}

	return &openapidedicatedv1beta1.ListNetworkContainersResponse{
		NetworkContainers: pagedContainers,
		TotalSize:         int32(len(openApiNetworkContainers)),
		NextPageToken:     nextToken,
	}, nil
}

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

	var (
		err     error
		reqMeta *metadata.RequestMeta
		cidr    *globaldedicatedv1.Cidr
	)

	detailsFunc := func() auditlog.Details {
		details := eventbus.NewAuditLogDetails(eventbus.TierTypeDedicated)
		details[auditlog.KeyRegion] = cidr.GetRegionName()
		details[auditlog.KeyCloudProvider] = cidr.GetCloudProviderName()
		details[auditlog.KeyRegionCIDR] = cidr.GetCidr()
		return details
	}
	defer func() {
		s.auditLogSender.SubmitAuditLog(ctx, auditlog.EventDeleteProjectCIDR, reqMeta, detailsFunc, err)
	}()

	// Extract request meta.
	reqMeta, err = metadata.ExtractRequestMetaWithCheckPermission(ctx)
	if err != nil {
		return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "failed to extract request metadata")
	}

	id, err := strconv.ParseUint(req.GetNetworkContainerId(), 10, 64)
	if err != nil {
		return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "invalid network container id")
	}

	// Get first.
	// Get the created cidr.
	globalCidrResp, err := s.dedicatedCli.QueryCidrs(ctx, &globaldedicatedv1.QueryCidrsRequest{
		Filter: &globaldedicatedv1.QueryCidrsRequest_QueryCidrsFilter{
			Ids: []uint64{id},
		},
	})
	if err != nil {
		return nil, bizerror.NewInternal(err, "failed to query created cidr")
	}
	if len(globalCidrResp.GetItems()) > 1 {
		err_ := fmt.Errorf("unexpected number of cidrs returned: %d", len(globalCidrResp.GetItems()))
		return nil, bizerror.NewInternal(err_, err_.Error())
	}
	if len(globalCidrResp.GetItems()) == 0 {
		err_ := fmt.Errorf("cidr not found")
		return nil, bizerror.NewNotFound(err_, err_.Error())
	}

	cidr = globalCidrResp.GetItems()[0]

	// Get the created cidr.
	_, err = s.dedicatedCli.DeleteCidr(ctx, &globaldedicatedv1.DeleteCidrRequest{
		Id:        cidr.Id,
		ProjectId: globalCidrResp.GetItems()[0].GetProjectId(),
	})
	if err != nil {
		return nil, err
	}

	return &emptypb.Empty{}, nil
}

func networkContainerGlobalToOpenApi(
	globalCidr *globaldedicatedv1.Cidr,
) (*openapidedicatedv1beta1.NetworkContainer, error) {
	_, id, p, err := common.BuildOpenApiRegionName(globalCidr.GetCloudProviderName(), globalCidr.GetRegionName())
	if err != nil {
		return nil, err
	}

	return &openapidedicatedv1beta1.NetworkContainer{
		Name:               fmt.Sprintf("networkContainers/%d", globalCidr.GetId()),
		NetworkContainerId: strconv.FormatUint(globalCidr.GetId(), 10),
		Labels: map[string]string{
			common.ProjectIDLabelKey: strconv.FormatUint(globalCidr.GetProjectId(), 10),
		},
		RegionId:          id,
		CidrNotation:      globalCidr.GetCidr(),
		CloudProvider:     p,
		State:             containerStateGlobalToOpenApi(globalCidr.GetState()),
		RegionDisplayName: globalCidr.GetRegionDisplayName(),
		VpcId:             globalCidr.GetVpcId(),
	}, nil
}

func containerStateGlobalToOpenApi(state globaldedicatedv1.Cidr_State) openapidedicatedv1beta1.NetworkContainer_State {
	switch state {
	case globaldedicatedv1.Cidr_STATE_ACTIVE:
		return openapidedicatedv1beta1.NetworkContainer_ACTIVE
	case globaldedicatedv1.Cidr_STATE_INACTIVE:
		return openapidedicatedv1beta1.NetworkContainer_INACTIVE
	default:
		return openapidedicatedv1beta1.NetworkContainer_STATE_UNSPECIFIED
	}
}
