package vpcpeering

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"
	"github.com/tidbcloud/tidb-management-service/utilities/enum"
	"github.com/tidbcloud/tidb-management-service/utilities/grpc/metadata"
)

func (c serverImpl) CreateVpcPeering(
	ctx context.Context,
	req *tidbmgmtservicev1.CreateVpcPeeringRequest,
) (*tidbmgmtservicev1.CreateVpcPeeringResponse, error) {
	if req.GetType() != globaldedicatedv1.VpcPeering_CUSTOMER_READONLY &&
		req.GetType() != globaldedicatedv1.VpcPeering_DBAAS {
		return nil, bizerr.New(bizerr.ErrCodeParams, "peering type is invalid")
	}

	if req.GetOrganizationId() == 0 || req.GetProjectId() == 0 {
		return nil, bizerr.New(bizerr.ErrCodeParams, "organization id or project id is invalid")
	}

	if req.GetSourceRegion() == req.GetTargetRegion() {
		return nil, bizerr.New(bizerr.ErrCodeParams, "source region and target region can not be the same")
	}

	// check provider and region
	cloudProviderName, err := enum.FromString[globalcommonv1.CloudProvider_Name](req.GetCloudProvider())
	if err != nil {
		return nil, bizerr.New(bizerr.ErrCodeParams, "cloud provider %s is not supported", req.GetCloudProvider())
	}

	sourceRegionName, err := enum.FromString[globalcommonv1.Region_Name](req.GetSourceRegion())
	if err != nil {
		return nil, bizerr.New(bizerr.ErrCodeParams, "source region %s is not supported", req.GetSourceRegion())
	}

	targetRegionName, err := enum.FromString[globalcommonv1.Region_Name](req.GetTargetRegion())
	if err != nil {
		return nil, bizerr.New(bizerr.ErrCodeParams, "target region %s is not supported", req.GetTargetRegion())
	}

	// gen request meta and append to incoming meta
	ctx = metadata.AppendToIncomingMeta(ctx, &metadata.RequestMeta{
		OrgID:            req.GetOrganizationId(),
		DefaultProjectID: req.GetProjectId(),
		ValidProjectList: []uint64{req.GetProjectId()},
		UserID:           "0", // system user
	})

	//  get tidb cloud vpc
	vpcResp, err := c.dedicatedCli.ListVpcs(ctx, &globaldedicatedv1.ListVpcsRequest{
		OrganizationId:   req.GetOrganizationId(),
		ProjectId:        req.GetProjectId(),
		CloudProviderIds: []globalcommonv1.CloudProvider_Name{globalcommonv1.CloudProvider_Name(cloudProviderName)},
		RegionIds: []globalcommonv1.Region_Name{
			globalcommonv1.Region_Name(sourceRegionName),
			globalcommonv1.Region_Name(targetRegionName),
		},
	})

	if err != nil {
		return nil, err
	}

	var (
		sourceVpc *globaldedicatedv1.Vpc
		targetVpc *globaldedicatedv1.Vpc
	)
	for _, vpc := range vpcResp.Vpcs {
		if vpc.GetRegionId() == globalcommonv1.Region_Name(sourceRegionName) {
			sourceVpc = vpc
		}
		if vpc.GetRegionId() == globalcommonv1.Region_Name(targetRegionName) {
			targetVpc = vpc
		}
	}

	if sourceVpc == nil {
		return nil, bizerr.New(bizerr.ErrCodeParams, "source vpc not found")
	}

	if targetVpc == nil {
		return nil, bizerr.New(bizerr.ErrCodeParams, "target vpc not found")
	}

	globalReq := &globaldedicatedv1.CreateVpcPeeringRequest{
		Peering: &globaldedicatedv1.VpcPeering{
			OrganizationId:     req.GetOrganizationId(),
			ProjectId:          req.GetProjectId(),
			CloudProvider:      globalcommonv1.CloudProvider_Name(cloudProviderName),
			Type:               req.GetType(),
			TidbCloudRegion:    req.SourceRegion,
			TidbCloudCidrBlock: sourceVpc.TidbCloudCidrBlock,
			CustomerRegion:     req.TargetRegion,
			CustomerCidrBlock:  targetVpc.TidbCloudCidrBlock,
		},
	}

	switch globalcommonv1.CloudProvider_Name(cloudProviderName) {
	case globalcommonv1.CloudProvider_AWS:
		globalReq.Peering.Aws = &globaldedicatedv1.VpcPeering_Aws{
			CustomerAccountId: targetVpc.GetAws().GetTidbCloudAccountId(),
			CustomerVpcId:     targetVpc.GetAws().GetTidbCloudVpcId(),
		}
	case globalcommonv1.CloudProvider_GCP:
		globalReq.Peering.Gcp = &globaldedicatedv1.VpcPeering_Gcp{
			CustomerProjectId:   targetVpc.GetGcp().GetTidbCloudProjectId(),
			CustomerNetworkName: targetVpc.GetGcp().GetTidbCloudNetworkName(),
		}
	case globalcommonv1.CloudProvider_AZURE:
		// TODO(@yifan): Wait azure vpc peering prd spec.
	}

	// create vpc peering
	peering, err := c.dedicatedCli.CreateVpcPeering(ctx, globalReq)

	if err != nil {
		return nil, err
	}

	return &tidbmgmtservicev1.CreateVpcPeeringResponse{
		PeeringId: peering.GetPeeringId(),
	}, nil
}

func (c serverImpl) ListVpcPeerings(
	ctx context.Context,
	req *tidbmgmtservicev1.ListVpcPeeringsRequest,
) (*tidbmgmtservicev1.ListVpcPeeringsResponse, error) {
	if req.GetOrganizationId() == 0 || req.GetProjectId() == 0 {
		return nil, bizerr.New(bizerr.ErrCodeParams, "organization id or project id is invalid")
	}

	// check provider and region
	cloudProviderName, err := enum.FromString[globalcommonv1.CloudProvider_Name](req.GetCloudProvider())
	if err != nil {
		return nil, bizerr.New(bizerr.ErrCodeParams, "cloud provider %s is not supported", req.GetCloudProvider())
	}

	regions := make([]globalcommonv1.Region_Name, 0, len(req.GetRegions()))
	for _, region := range req.GetRegions() {
		regionName, err := enum.FromString[globalcommonv1.Region_Name](region)
		if err != nil {
			return nil, bizerr.New(bizerr.ErrCodeParams, "region %s is not supported", region)
		}
		regions = append(regions, globalcommonv1.Region_Name(regionName))
	}

	ctx = metadata.AppendToIncomingMeta(ctx, &metadata.RequestMeta{
		OrgID:            req.GetOrganizationId(),
		DefaultProjectID: req.GetProjectId(),
		ValidProjectList: []uint64{req.GetProjectId()},
		UserID:           "0", // system user
	})

	listReq := &globaldedicatedv1.ListVpcPeeringsRequest{
		OrganizationId: req.GetOrganizationId(),
		CloudProviders: []globalcommonv1.CloudProvider_Name{globalcommonv1.CloudProvider_Name(cloudProviderName)},
		Regions:        regions,
		States:         req.GetStates(),
		Types:          req.GetTypes(),
	}

	if req.GetProjectId() != 0 {
		listReq.ProjectIds = []uint64{req.GetProjectId()}
	}

	peerings, err := c.dedicatedCli.ListVpcPeerings(ctx, listReq)
	if err != nil {
		return nil, err
	}

	return &tidbmgmtservicev1.ListVpcPeeringsResponse{
		VpcPeerings: peerings.VpcPeerings,
	}, nil
}

func (c serverImpl) GetVpcPeering(
	ctx context.Context,
	req *tidbmgmtservicev1.GetVpcPeeringRequest,
) (*tidbmgmtservicev1.GetVpcPeeringResponse, error) {
	if req.GetOrganizationId() == 0 || req.GetProjectId() == 0 {
		return nil, bizerr.New(bizerr.ErrCodeParams, "organization id or project id is invalid")
	}

	// check provider and region
	cloudProviderName, err := enum.FromString[globalcommonv1.CloudProvider_Name](req.GetCloudProvider())
	if err != nil {
		return nil, bizerr.New(bizerr.ErrCodeParams, "cloud provider %s is not supported", req.GetCloudProvider())
	}

	// gen request meta and append to incoming meta
	ctx = metadata.AppendToIncomingMeta(ctx, &metadata.RequestMeta{
		OrgID:            req.GetOrganizationId(),
		DefaultProjectID: req.GetProjectId(),
		ValidProjectList: []uint64{req.GetProjectId()},
		UserID:           "0", // system user
	})

	peering, err := c.dedicatedCli.GetVpcPeering(ctx, &globaldedicatedv1.GetVpcPeeringRequest{
		VpcPeeringId:  req.GetVpcPeeringId(),
		CloudProvider: globalcommonv1.CloudProvider_Name(cloudProviderName),
	})

	if err != nil {
		return nil, err
	}

	if req.GetOrganizationId() != 0 && peering.OrganizationId != req.GetOrganizationId() {
		return nil, bizerr.New(
			bizerr.ErrCodeParams,
			"the peering does not belong to the organization %d",
			req.GetOrganizationId(),
		)
	}

	if req.GetProjectId() != 0 && peering.ProjectId != req.GetProjectId() {
		return nil, bizerr.New(
			bizerr.ErrCodeParams,
			"the peering does not belong to the project %d",
			req.GetProjectId(),
		)
	}

	return &tidbmgmtservicev1.GetVpcPeeringResponse{
		VpcPeering: peering,
	}, nil
}

func (c serverImpl) DeleteVpcPeering(
	ctx context.Context,
	req *tidbmgmtservicev1.DeleteVpcPeeringRequest,
) (*emptypb.Empty, error) {
	if req.GetOrganizationId() == 0 || req.GetProjectId() == 0 {
		return nil, bizerr.New(bizerr.ErrCodeParams, "organization id or project id is invalid")
	}

	// check provider and region
	cloudProviderName, err := enum.FromString[globalcommonv1.CloudProvider_Name](req.GetCloudProvider())
	if err != nil {
		return nil, bizerr.New(bizerr.ErrCodeParams, "cloud provider %s is not supported", req.GetCloudProvider())
	}

	// gen request meta and append to incoming meta
	ctx = metadata.AppendToIncomingMeta(ctx, &metadata.RequestMeta{
		OrgID:            req.GetOrganizationId(),
		DefaultProjectID: req.GetProjectId(),
		ValidProjectList: []uint64{req.GetProjectId()},
		UserID:           "0", // system user
	})

	vpcpeeringResp, err := c.GetVpcPeering(ctx, &tidbmgmtservicev1.GetVpcPeeringRequest{
		OrganizationId: req.GetOrganizationId(),
		ProjectId:      req.GetProjectId(),
		VpcPeeringId:   req.GetVpcPeeringId(),
		CloudProvider:  req.GetCloudProvider(),
	})

	if err != nil {
		return nil, err
	}

	if vpcpeeringResp.VpcPeering == nil {
		return nil, bizerr.New(bizerr.ErrCodeRecordNotFound, "vpc peering not found")
	}

	_, err = c.dedicatedCli.DeleteVpcPeering(ctx, &globaldedicatedv1.DeleteVpcPeeringRequest{
		VpcPeeringId:  req.GetVpcPeeringId(),
		CloudProvider: globalcommonv1.CloudProvider_Name(cloudProviderName),
	})

	if err != nil {
		return nil, err
	}

	return &emptypb.Empty{}, nil
}
