package central

import (
	"fmt"
	"strconv"
	"strings"

	"github.com/gin-gonic/gin"
	centralapi "github.com/tidbcloud/core-svc/idl/pbgen/proto/central"

	globaldedicatedv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/dedicated/v1"
	"github.com/tidbcloud/tidb-management-service/tempo-wrapper/internal/api/types"
	"github.com/tidbcloud/tidb-management-service/tempo-wrapper/pkg/grpc"
	dedicatedgrpc "github.com/tidbcloud/tidb-management-service/tempo-wrapper/pkg/grpc/dedicated"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
)

func CreateCidr(ctx *gin.Context) {
	req := &centralapi.CreateCidrRequest{}
	if err := types.ShouldBindJSON(ctx, req); err != nil {
		types.BadRequestResp(ctx, fmt.Errorf("bind request body failed: %w", err))
		return
	}
	projectID, err := parseUintParam(ctx, "project_id")
	if err != nil {
		types.BadRequestResp(ctx, err)
		return
	}
	if _, err := parseUintParam(ctx, "org_id"); err != nil {
		types.BadRequestResp(ctx, err)
		return
	}
	if req.Region == nil {
		types.BadRequestResp(ctx, fmt.Errorf("region is required"))
		return
	}

	resp, err := dedicatedgrpc.CreateCidr(grpc.NewContext(ctx), &globaldedicatedv1.CreateCidrRequest{
		ProjectId:         projectID,
		Cidr:              req.Cidr,
		CloudProviderName: providerToDedicated(req.Region.Provider),
		RegionName:        req.Region.Name,
		ValidateOnly:      req.ValidateOnly,
	})
	if err != nil {
		types.GrpcErrorResp(ctx, err)
		return
	}

	types.SuccessResp(ctx, &centralapi.CreateCidrResponse{
		Id: resp.Id, Overlaps: overlapsToCentral(resp.Overlaps), MaxNodeIp: resp.MaxNodeIp,
		BaseResp: successBaseResp(),
	})
}

func ListCidrs(ctx *gin.Context) {
	projectID, err := parseUintParam(ctx, "project_id")
	if err != nil {
		types.BadRequestResp(ctx, err)
		return
	}
	provider, err := providerFromString(ctx.Query("provider"))
	if err != nil {
		types.BadRequestResp(ctx, err)
		return
	}

	resp, err := dedicatedgrpc.QueryCidrs(grpc.NewContext(ctx), &globaldedicatedv1.QueryCidrsRequest{
		Filter: &globaldedicatedv1.QueryCidrsRequest_QueryCidrsFilter{
			ProjectIds: []uint64{projectID}, CloudProviderName: providerToDedicated(provider),
		},
	})
	if err != nil {
		types.GrpcErrorResp(ctx, err)
		return
	}
	items := make([]*centralapi.Cidr, 0, len(resp.Items))
	for _, item := range resp.Items {
		items = append(items, cidrToCentral(item))
	}
	types.SuccessResp(ctx, &centralapi.ListCidrsResponse{
		Items: items, Total: int32(len(items)), BaseResp: successBaseResp(),
	})
}

func GetCidr(ctx *gin.Context) {
	projectID, cidrID, ok := parseCidrIDs(ctx)
	if !ok {
		return
	}
	resp, err := dedicatedgrpc.QueryCidrs(grpc.NewContext(ctx), &globaldedicatedv1.QueryCidrsRequest{
		Filter: &globaldedicatedv1.QueryCidrsRequest_QueryCidrsFilter{
			Ids: []uint64{cidrID}, ProjectIds: []uint64{projectID},
		},
	})
	if err != nil {
		types.GrpcErrorResp(ctx, err)
		return
	}
	if len(resp.Items) != 1 {
		types.BizErrorRequestResp(ctx, bizerr.New(bizerr.ErrCodeRecordNotFound, "cidr not found"))
		return
	}
	types.SuccessResp(ctx, &centralapi.GetCidrResponse{Cidr: cidrToCentral(resp.Items[0]), BaseResp: successBaseResp()})
}

func UpdateCidr(ctx *gin.Context) {
	projectID, cidrID, ok := parseCidrIDs(ctx)
	if !ok {
		return
	}
	req := &centralapi.UpdateCidrRequest{}
	if err := types.ShouldBindJSON(ctx, req); err != nil {
		types.BadRequestResp(ctx, fmt.Errorf("bind request body failed: %w", err))
		return
	}
	resp, err := dedicatedgrpc.UpdateCidr(grpc.NewContext(ctx), &globaldedicatedv1.UpdateCidrRequest{
		Id: cidrID, ProjectId: projectID, Cidr: req.Cidr, ValidateOnly: req.ValidateOnly,
	})
	if err != nil {
		types.GrpcErrorResp(ctx, err)
		return
	}
	types.SuccessResp(ctx, &centralapi.UpdateCidrResponse{
		Overlaps: overlapsToCentral(resp.Overlaps), MaxNodeIp: resp.MaxNodeIp, BaseResp: successBaseResp(),
	})
}

func DeleteCidr(ctx *gin.Context) {
	projectID, cidrID, ok := parseCidrIDs(ctx)
	if !ok {
		return
	}
	if _, err := dedicatedgrpc.DeleteCidr(grpc.NewContext(ctx), &globaldedicatedv1.DeleteCidrRequest{
		Id: cidrID, ProjectId: projectID,
	}); err != nil {
		types.GrpcErrorResp(ctx, err)
		return
	}
	types.SuccessResp(ctx, &centralapi.DefaultResp{BaseResp: successBaseResp()})
}

func GetDefaultCidr(ctx *gin.Context) {
	projectID, err := parseUintParam(ctx, "project_id")
	if err != nil {
		types.BadRequestResp(ctx, err)
		return
	}
	provider, err := providerFromString(ctx.Query("provider"))
	if err != nil {
		types.BadRequestResp(ctx, err)
		return
	}
	resp, err := dedicatedgrpc.GetDefaultCidr(grpc.NewContext(ctx), &globaldedicatedv1.GetDefaultCidrRequest{
		ProjectId: projectID, CloudProviderName: providerToDedicated(provider), RegionName: ctx.Query("region"),
	})
	if err != nil {
		types.GrpcErrorResp(ctx, err)
		return
	}
	out := &centralapi.GetDefaultCidrResponse{BaseResp: successBaseResp()}
	if cidr := resp.DefaultCidr; cidr != nil {
		out.Name = cidr.Name
		out.Provider, err = providerFromString(cidr.CloudProviderName)
		if err != nil {
			types.InternalErrorResp(ctx, err)
			return
		}
		out.Region = &centralapi.CloudRegion{Name: cidr.RegionName, DisplayName: cidr.RegionDisplayName, Provider: out.Provider}
		out.Cidr = cidr.Cidr
		out.ProjectId = cidr.ProjectId
	}
	types.SuccessResp(ctx, out)
}

func parseCidrIDs(ctx *gin.Context) (projectID, cidrID uint64, ok bool) {
	projectID, err := parseUintParam(ctx, "project_id")
	if err != nil {
		types.BadRequestResp(ctx, err)
		return 0, 0, false
	}
	cidrID, err = parseUintParam(ctx, "cidr_id")
	if err != nil {
		types.BadRequestResp(ctx, err)
		return 0, 0, false
	}
	return projectID, cidrID, true
}

func parseUintParam(ctx *gin.Context, name string) (uint64, error) {
	value, err := strconv.ParseUint(ctx.Param(name), 10, 64)
	if err != nil {
		return 0, fmt.Errorf("invalid %s: %w", name, err)
	}
	return value, nil
}

func providerFromString(value string) (centralapi.CloudRegion_CloudProvider, error) {
	provider, ok := centralapi.CloudRegion_CloudProvider_value[strings.ToUpper(value)]
	if !ok || provider == int32(centralapi.CloudRegion_CLOUD_PROVIDER_UNSPECIFIED) {
		return centralapi.CloudRegion_CLOUD_PROVIDER_UNSPECIFIED, fmt.Errorf("unsupported cloud provider %q", value)
	}
	return centralapi.CloudRegion_CloudProvider(provider), nil
}

func providerToDedicated(provider centralapi.CloudRegion_CloudProvider) string {
	return strings.ToLower(provider.String())
}

func cidrToCentral(cidr *globaldedicatedv1.Cidr) *centralapi.Cidr {
	provider, _ := providerFromString(cidr.CloudProviderName)
	return &centralapi.Cidr{
		Id: cidr.Id, ProjectId: cidr.ProjectId, Cidr: cidr.Cidr,
		Region:   &centralapi.CloudRegion{Name: cidr.RegionName, DisplayName: cidr.RegionDisplayName, Provider: provider},
		Provider: provider, State: centralapi.Cidr_State(cidr.State), VpcId: cidr.VpcId,
	}
}

func overlapsToCentral(overlaps []*globaldedicatedv1.CidrOverlap) []*centralapi.CidrOverlap {
	out := make([]*centralapi.CidrOverlap, 0, len(overlaps))
	for _, overlap := range overlaps {
		item := &centralapi.CidrOverlap{ProjectId: overlap.ProjectId}
		for _, region := range overlap.Regions {
			provider, _ := providerFromString(region.CloudProviderName)
			item.Regions = append(item.Regions, &centralapi.CloudRegion{
				Name: region.Name, DisplayName: region.DisplayName, Provider: provider,
			})
		}
		out = append(out, item)
	}
	return out
}

func successBaseResp() *centralapi.BaseResp { return &centralapi.BaseResp{} }
