package checker

import (
	"context"
	"fmt"
	"strconv"

	"github.com/samber/lo"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
	"google.golang.org/grpc"

	globaldedicatedv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/dedicated/v1"
	"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/grpc/metadata"
)

type networkContainerId interface {
	GetNetworkContainerId() string
}

func NetworkContainerIdChecker(
	ctx context.Context,
	checkCtx *CheckContext,
	req any,
	_ *grpc.UnaryServerInfo,
) (context.Context, any, error) {
	reqMeta, err := metadata.ExtractRequestMeta(ctx)
	if err != nil {
		tilog.Error(ctx, "failed to extract request metadata", zap.Error(err))
		return ctx, req, bizerror.NewInternal(err, "failed to extract request metadata")
	}

	ncReq, ok := req.(networkContainerId)
	if !ok {
		return ctx, req, bizerr.New(bizerr.ErrCodeParams, "invalid request, req=%v", req)
	}
	ncIdStr := ncReq.GetNetworkContainerId()

	if ncIdStr == "" {
		return ctx, req, bizerr.New(bizerr.ErrCodeParams, "invalid network container id %s", ncIdStr)
	}

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

	coreCidrResp, err := checkCtx.DedicatedCli.QueryCidrs(ctx, &globaldedicatedv1.QueryCidrsRequest{
		Filter: &globaldedicatedv1.QueryCidrsRequest_QueryCidrsFilter{
			Ids: []uint64{ncId},
		},
	})
	if err != nil {
		return ctx, req, bizerror.NewInternal(err, fmt.Sprintf("failed to load maintenance task %d", ncId))
	}
	if len(coreCidrResp.GetItems()) > 1 {
		err_ := fmt.Errorf("unexpected number of cidrs returned: %d", len(coreCidrResp.GetItems()))
		return ctx, req, bizerror.NewInternal(err_, err_.Error())
	}
	if len(coreCidrResp.GetItems()) == 0 {
		err_ := fmt.Errorf("cidr not found")
		return ctx, req, bizerror.NewNotFound(err_, err_.Error())
	}

	// Check permission
	if err := reqMeta.CheckProjectIDsPermission(coreCidrResp.GetItems()[0].GetProjectId()); err != nil {
		return ctx, req, bizerr.WrapErrorWithCode(err, bizerr.ErrCodePermission, bizerr.ErrMsgResourceNoPermission)
	}

	reqMeta.IsCheckPermission = lo.ToPtr(true)
	reqMeta.DefaultProjectID = coreCidrResp.GetItems()[0].GetProjectId()

	return metadata.AppendToIncomingMeta(ctx, reqMeta), req, nil
}

type vpcPeeringId interface {
	GetVpcPeeringId() string
}

func VpcPeeringIdChecker(
	ctx context.Context,
	checkCtx *CheckContext,
	req any,
	_ *grpc.UnaryServerInfo,
) (context.Context, any, error) {
	reqMeta, err := metadata.ExtractRequestMeta(ctx)
	if err != nil {
		tilog.Error(ctx, "failed to extract request metadata", zap.Error(err))
		return ctx, req, bizerror.NewInternal(err, "failed to extract request metadata")
	}

	vpReq, ok := req.(vpcPeeringId)
	if !ok {
		return ctx, req, bizerr.New(bizerr.ErrCodeParams, "invalid request: req=%v", req)
	}
	vpIdStr := vpReq.GetVpcPeeringId()

	if vpIdStr == "" {
		return ctx, req, bizerr.New(bizerr.ErrCodeParams, "vpc peering id is empty")
	}

	p, vpId, err := common.ParseVpcPeeringOpenApiId(vpIdStr)
	if err != nil {
		return ctx, req, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "invalid vpc peering id %s", vpIdStr)
	}

	resp, err := checkCtx.DedicatedCli.GetVpcPeering(ctx, &globaldedicatedv1.GetVpcPeeringRequest{
		VpcPeeringId:  vpId,
		CloudProvider: common.OpenApiProviderToGlobal(p),
	})
	if err != nil {
		return ctx, req, bizerr.WrapGrpcError(err, "failed to get vpc peering")
	}

	// Check permission
	if err := reqMeta.CheckProjectIDsPermission(resp.GetProjectId()); err != nil {
		return ctx, req, bizerr.WrapErrorWithCode(err, bizerr.ErrCodePermission, bizerr.ErrMsgResourceNoPermission)
	}

	reqMeta.IsCheckPermission = lo.ToPtr(true)
	reqMeta.DefaultProjectID = resp.GetProjectId()

	return metadata.AppendToIncomingMeta(ctx, reqMeta), req, nil
}
