package cluster

import (
	"context"
	"fmt"

	"github.com/gogo/protobuf/types"
	"github.com/pingcap/dbaas/central/config"
	"github.com/pingcap/dbaas/central/db/entity"
	"github.com/pingcap/dbaas/central/grpc/grpcli"
	"github.com/pingcap/dbaas/central/model/cluster"
	"github.com/pingcap/dbaas/central/service/cvt"
	"github.com/pingcap/errcode"
	"github.com/tidbcloud/idl/pbgen/proto/base"
	"github.com/tidbcloud/idl/pbgen/proto/central"
	"github.com/tidbcloud/idl/pbgen/proto/core"
)

func (s *clusterService) CreateAWSVpcPeering(c context.Context, req *central.CreateAWSVpcPeeringReq) (*central.CreateAWSVpcPeeringResp, error) {
	orgID := entity.ID(req.OrgId)
	projectID := entity.ID(req.ProjectId)
	userContext := s.userCluster(c)
	err := userContext.CheckOrgIDAndProjectID(orgID, projectID)
	if err != nil {
		return nil, err
	}

	peeringId, err := s.userCluster(c).CreateNetworkPeer(c, &cluster.CreateNetworkPeerParams{
		ProjectId:     projectID.Uint64(),
		CloudProvider: entity.Aws.String(),
		Region:        req.Region,
		PeerRegion:    req.TenantRegion,
		PeerVpcCidr:   req.TenantVpcCidr,
		PeerNetwork:   req.TenantVpcId,
		Type:          core.NetworkPeer_TYPE_CUSTOMER,
		Aws: cluster.CreateNetworkPeerParams4Aws{
			PeerAccountId: req.TenantAccountId,
		},
	})
	if err != nil {
		return nil, err
	}
	peering, err := grpcli.GetNetworkPeer(c, entity.Aws.String(), peeringId)
	if err != nil {
		return nil, err
	}
	return &central.CreateAWSVpcPeeringResp{
		VpcPeeringId:    peering.Aws.VpcPeeringId,
		VpcCidr:         peering.VpcCidr,
		PrivateHostZone: peering.Aws.PrivateHostZone,
		Region:          peering.Region,
		Id:              peering.Id,
	}, nil
}

func (s *clusterService) CreateGcpVpcPeering(c context.Context, req *central.CreateGcpVpcPeeringReq) (*central.CreateGcpVpcPeeringResp, error) {
	orgID := entity.ID(req.OrgId)
	projectID := entity.ID(req.ProjectId)
	userContext := s.userCluster(c)
	err := userContext.CheckOrgIDAndProjectID(orgID, projectID)
	if err != nil {
		return nil, err
	}

	peeringId, err := s.userCluster(c).CreateNetworkPeer(c, &cluster.CreateNetworkPeerParams{
		ProjectId:     projectID.Uint64(),
		CloudProvider: entity.Gcp.String(),
		Region:        req.Region,
		PeerRegion:    req.TenantVpcRegion,
		PeerVpcCidr:   req.TenantVpcCidr,
		PeerNetwork:   req.TenantNetworkName,
		Type:          core.NetworkPeer_TYPE_CUSTOMER,
		Gcp: cluster.CreateNetworkPeerParams4Gcp{
			PeerProjectId: req.TenantProjectId,
		},
	})
	if err != nil {
		return nil, err
	}
	peering, err := grpcli.GetNetworkPeer(c, entity.Gcp.String(), peeringId)
	if err != nil {
		return nil, err
	}
	return &central.CreateGcpVpcPeeringResp{
		ProjectId:   config.Global().InfraConfig.GcpProject, // FIXME(chenfei): do we need?
		NetworkName: peering.Network,
		Region:      peering.Region,
		Id:          peering.Id,
	}, nil
}

func (s *clusterService) ListAwsVpcPeerings(c context.Context, req *central.ProjectIdReq) (*central.AwsVpcPeerings, error) {
	orgID := entity.ID(req.OrgId)
	projectID := entity.ID(req.ProjectId)
	userContext := s.userCluster(c)
	err := userContext.CheckOrgIDAndProjectID(orgID, projectID)
	if err != nil {
		return nil, err
	}

	peeringList, err := listVpcPeerings(c, entity.Aws.String(), req)
	if err != nil {
		return nil, err
	}
	var items = make([]*central.AwsVpcPeering, len(peeringList))
	for idx, item := range peeringList {
		items[idx] = cvt.AwsVpcPeering(item)
	}
	return &central.AwsVpcPeerings{
		Total: int32(len(items)),
		Items: items,
	}, nil
}

func (s *clusterService) ListGcpVpcPeerings(c context.Context, req *central.ProjectIdReq) (*central.GcpVpcPeerings, error) {
	orgID := entity.ID(req.OrgId)
	projectID := entity.ID(req.ProjectId)
	userContext := s.userCluster(c)
	err := userContext.CheckOrgIDAndProjectID(orgID, projectID)
	if err != nil {
		return nil, err
	}

	peeringList, err := listVpcPeerings(c, entity.Gcp.String(), req)
	if err != nil {
		return nil, err
	}
	var items = make([]*central.GcpVpcPeering, len(peeringList))
	for idx, item := range peeringList {
		items[idx] = cvt.GcpVpcPeering(item)
	}
	return &central.GcpVpcPeerings{
		Total: int32(len(items)),
		Items: items,
	}, nil
}

func listVpcPeerings(ctx context.Context, cloudProvider string, req *central.ProjectIdReq) ([]*core.NetworkPeer, error) {
	filter := &core.NetworkPeerFilter{
		CloudProvider: cloudProvider,
		Types:         []core.NetworkPeer_Type{core.NetworkPeer_TYPE_CUSTOMER},
		ProjectIds:    []uint64{req.ProjectId},
	}
	if req.Region != "" {
		filter.RegionNames = []string{req.Region}
	}
	sorter := &base.Sorter{
		OrderBy: "created_at",
		Sort:    base.Sort_SORT_DESC,
	}
	peeringList, err := grpcli.ListNetworkPeer(ctx, filter, nil, sorter)
	if err != nil {
		return nil, err
	}

	return peeringList, nil
}

func (s *clusterService) GetAwsVpcPeering(c context.Context, req *central.VpcpeeringID) (*central.AwsVpcPeering, error) {
	orgID := entity.ID(req.OrgId)
	projectID := entity.ID(req.ProjectId)
	userContext := s.userCluster(c)
	err := userContext.CheckOrgIDAndProjectID(orgID, projectID)
	if err != nil {
		return nil, err
	}

	peering, err := grpcli.GetNetworkPeer(c, entity.Aws.String(), req.Id)
	if err != nil {
		return nil, err
	}
	if peering.ProjectId != req.ProjectId {
		return nil, errcode.NewForbiddenErr(fmt.Errorf("invalid project id"))
	}
	return cvt.AwsVpcPeering(peering), nil
}

func (s *clusterService) GetGcpVpcPeering(c context.Context, req *central.VpcpeeringID) (*central.GcpVpcPeering, error) {
	orgID := entity.ID(req.OrgId)
	projectID := entity.ID(req.ProjectId)
	userContext := s.userCluster(c)
	err := userContext.CheckOrgIDAndProjectID(orgID, projectID)
	if err != nil {
		return nil, err
	}

	peering, err := grpcli.GetNetworkPeer(c, entity.Gcp.String(), req.Id)
	if err != nil {
		return nil, err
	}
	if peering.ProjectId != req.ProjectId {
		return nil, errcode.NewForbiddenErr(fmt.Errorf("invalid project id"))
	}
	return cvt.GcpVpcPeering(peering), nil
}

func (s *clusterService) DeleteAwsVpcPeering(c context.Context, req *central.VpcpeeringID) (*types.Empty, error) {
	orgID := entity.ID(req.OrgId)
	projectID := entity.ID(req.ProjectId)
	userContext := s.userCluster(c)
	err := userContext.CheckOrgIDAndProjectID(orgID, projectID)
	if err != nil {
		return nil, err
	}
	vpcPeering, err := grpcli.GetNetworkPeer(c, entity.Aws.String(), req.Id)
	if err != nil {
		return nil, err
	}
	if vpcPeering.ProjectId != req.ProjectId {
		return nil, errcode.NewForbiddenErr(fmt.Errorf("invalid project id"))
	}

	if err := grpcli.DeleteNetworkPeer(c, entity.Aws.String(), req.Id, userContext.UserID()); err != nil {
		return nil, err
	}
	return &types.Empty{}, nil
}

func (s *clusterService) DeleteGcpVpcPeering(c context.Context, req *central.VpcpeeringID) (*types.Empty, error) {
	orgID := entity.ID(req.OrgId)
	projectID := entity.ID(req.ProjectId)
	userContext := s.userCluster(c)
	err := userContext.CheckOrgIDAndProjectID(orgID, projectID)
	if err != nil {
		return nil, err
	}
	vpcPeering, err := grpcli.GetNetworkPeer(c, entity.Gcp.String(), req.Id)
	if err != nil {
		return nil, err
	}
	if vpcPeering.ProjectId != req.ProjectId {
		return nil, errcode.NewForbiddenErr(fmt.Errorf("invalid project id"))
	}

	if err := grpcli.DeleteNetworkPeer(c, entity.Gcp.String(), req.Id, userContext.UserID()); err != nil {
		return nil, err
	}
	return &types.Empty{}, nil
}
