package resourcepool

import (
	"maps"
	"strings"
	"time"

	"google.golang.org/protobuf/types/known/timestamppb"
	"gorm.io/gorm"

	globalcommonv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/common/v1"
	globalngv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/nextgen/v1"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/entity"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/infra/repoimpl/schema"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
	"github.com/tidbcloud/tidb-management-service/utilities/enum"
)

func enToModel(resourcePool *entity.ResourcePool) (*schema.ResourcePool, bizerr.BizError) {
	if resourcePool == nil {
		return nil, nil //nolint:nilnil // ignore nil check
	}
	if resourcePool.OwnerType == globalngv1.ResourcePool_OWNER_TYPE_UNSPECIFIED {
		return nil, bizerr.New(bizerr.ErrCodeParams, "owner type cannot be unspecified")
	}

	var cidrStartIPv4, cidrEndIPv4 []byte
	if strings.TrimSpace(resourcePool.GetCidr()) != "" {
		var err error
		cidrStartIPv4, cidrEndIPv4, err = entity.CanonicalIPv4Range(resourcePool.GetCidr())
		if err != nil {
			// CIDR syntax is required to derive the persisted IPv4 bounds for
			// every service plan. BYOC-only checks happen at the service layer.
			return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "invalid resource pool CIDR")
		}
	}

	lifecycleState := ""
	if resourcePool.GetLifecycleState() != globalngv1.ResourcePool_LIFECYCLE_STATE_UNSPECIFIED {
		lifecycleState = resourcePool.GetLifecycleState().String()
	}
	vcpuLimitMode := globalngv1.ResourcePool_INHERITED.String()
	if resourcePool.GetVcpuLimitMode() != globalngv1.ResourcePool_VCPU_LIMIT_MODE_UNSPECIFIED {
		vcpuLimitMode = resourcePool.GetVcpuLimitMode().String()
	}

	var createdBy *string
	if value := resourcePool.GetCreatedBy(); value != "" {
		createdBy = &value
	}
	var pausedAt *time.Time
	if value := resourcePool.GetPausedAt(); value != nil && value.IsValid() {
		converted := value.AsTime()
		pausedAt = &converted
	}
	var automaticResumeAt *time.Time
	if value := resourcePool.GetAutomaticResumeAt(); value != nil && value.IsValid() {
		converted := value.AsTime()
		automaticResumeAt = &converted
	}

	return &schema.ResourcePool{
		ID:                    resourcePool.Id,
		Name:                  resourcePool.Name,
		NormalizedName:        entity.NormalizeResourcePoolName(resourcePool.Name),
		ProviderId:            resourcePool.CloudProviderId,
		RegionId:              resourcePool.RegionId,
		OwnerType:             int(resourcePool.OwnerType),
		OwnerId:               resourcePool.OwnerId,
		Cidr:                  resourcePool.Cidr,
		CidrStartIPv4:         cidrStartIPv4,
		CidrEndIPv4:           cidrEndIPv4,
		K8sServiceCidr:        resourcePool.K8SServiceCidr,
		InstanceLimit:         resourcePool.InstanceLimit,
		VcpusLimit:            resourcePool.VcpusLimit,
		LifecycleState:        lifecycleState,
		StateMessageCode:      resourcePool.GetStateMessageCode(),
		VcpuLimitMode:         vcpuLimitMode,
		CloudResourceTags:     maps.Clone(resourcePool.GetCloudResourceTags()),
		CreatedBy:             createdBy,
		PausedAt:              pausedAt,
		AutomaticResumeAt:     automaticResumeAt,
		ServicePlan:           resourcePool.ServicePlan.String(),
		ProviderIdentityId:    resourcePool.GetProviderIdentityId(),
		DataplaneId:           resourcePool.GetDataplaneId(),
		PcaArn:                resourcePool.GetPcaArn(),
		PlacementGroupEnabled: resourcePool.GetPlacementGroupEnabled(),
		HighAvailabilityType:  resourcePool.GetHighAvailabilityType().String(),
	}, nil
}

func modelToEn(model *schema.ResourcePool) (*entity.ResourcePool, bizerr.BizError) {
	if model == nil {
		return nil, nil //nolint:nilnil // ignore nil check
	}
	lifecycleState, lifecycleErr := lifecycleStateFromModel(model.LifecycleState)
	if lifecycleErr != nil {
		return nil, lifecycleErr
	}
	vcpuLimitMode, vcpuLimitModeErr := vcpuLimitModeFromModel(model.VcpuLimitMode)
	if vcpuLimitModeErr != nil {
		return nil, vcpuLimitModeErr
	}

	createdBy := ""
	if model.CreatedBy != nil {
		createdBy = *model.CreatedBy
	}

	en := &entity.ResourcePool{
		ResourcePool: &globalngv1.ResourcePool{
			Id:                    model.ID,
			Name:                  model.Name,
			OwnerType:             globalngv1.ResourcePool_OwnerType(model.OwnerType),
			OwnerId:               model.OwnerId,
			Cidr:                  model.Cidr,
			K8SServiceCidr:        model.K8sServiceCidr,
			InstanceLimit:         model.InstanceLimit,
			VcpusLimit:            model.VcpusLimit,
			LifecycleState:        lifecycleState,
			StateMessageCode:      model.StateMessageCode,
			VcpuLimitMode:         vcpuLimitMode,
			CloudResourceTags:     maps.Clone(model.CloudResourceTags),
			CreatedBy:             createdBy,
			PausedAt:              timestampOrNil(model.PausedAt),
			AutomaticResumeAt:     timestampOrNil(model.AutomaticResumeAt),
			ServicePlan:           globalcommonv1.ServicePlan(globalcommonv1.ServicePlan_value[model.ServicePlan]),
			ProviderIdentityId:    model.ProviderIdentityId,
			DataplaneId:           model.DataplaneId,
			PcaArn:                model.PcaArn,
			PlacementGroupEnabled: model.PlacementGroupEnabled,
			HighAvailabilityType: globalngv1.ResourcePool_HighAvailabilityType(
				globalngv1.ResourcePool_HighAvailabilityType_value[model.HighAvailabilityType],
			),

			CreateTime: timestamppb.New(model.CreatedAt),
			UpdateTime: timestamppb.New(model.UpdatedAt),
		},
		RegionId:        uint64(model.Region.ID),
		CloudProviderId: uint64(model.Provider.ID),
		NormalizedName:  model.NormalizedName,
	}
	if len(model.CidrStartIPv4) == 4 && len(model.CidrEndIPv4) == 4 {
		en.CanonicalCIDR = &entity.IPv4Range{
			Start: [4]byte(model.CidrStartIPv4),
			End:   [4]byte(model.CidrEndIPv4),
		}
	}

	if model.DeletedAt.Valid {
		en.DeleteTime = timestamppb.New(model.DeletedAt.Time)
	} else {
		en.DeleteTime = nil
	}

	provider, err := enum.FromString[globalcommonv1.CloudProvider_Name](model.Provider.Name)
	if err != nil {
		return nil, bizerr.WrapError(err, "convert cloud provider")
	}
	en.ProviderId = globalcommonv1.CloudProvider_Name(provider)
	regionId, err := enum.RegionFromString(model.Provider.Name, model.Region.RegionName)
	if err != nil || regionId == globalcommonv1.Region_NAME_UNSPECIFIED {
		return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "invalid region: %s", model.Region.RegionName)
	}

	en.ResourcePool.RegionId = regionId

	return en, nil
}

func timestampOrNil(value *time.Time) *timestamppb.Timestamp {
	if value == nil {
		return nil
	}
	return timestamppb.New(*value)
}

func lifecycleStateFromModel(value string) (globalngv1.ResourcePool_LifecycleState, bizerr.BizError) {
	if value == "" {
		return globalngv1.ResourcePool_LIFECYCLE_STATE_UNSPECIFIED, nil
	}
	state, ok := globalngv1.ResourcePool_LifecycleState_value[value]
	if !ok {
		return globalngv1.ResourcePool_LIFECYCLE_STATE_UNSPECIFIED,
			bizerr.New(bizerr.ErrCodeUnknown, "invalid persisted resource pool lifecycle state")
	}
	return globalngv1.ResourcePool_LifecycleState(state), nil
}

func vcpuLimitModeFromModel(value string) (globalngv1.ResourcePool_VcpuLimitMode, bizerr.BizError) {
	if value == "" {
		return globalngv1.ResourcePool_INHERITED, nil
	}
	mode, ok := globalngv1.ResourcePool_VcpuLimitMode_value[value]
	if !ok {
		return globalngv1.ResourcePool_VCPU_LIMIT_MODE_UNSPECIFIED,
			bizerr.New(bizerr.ErrCodeUnknown, "invalid persisted resource pool vCPU limit mode")
	}
	return globalngv1.ResourcePool_VcpuLimitMode(mode), nil
}

func enToModelStrategy(strategy *entity.ResourcePoolStrategy) (*schema.ResourcePoolStrategy, bizerr.BizError) {
	if strategy == nil {
		return &schema.ResourcePoolStrategy{}, nil
	}
	if strategy.CloudProviderId == 0 {
		return nil, bizerr.New(bizerr.ErrCodeParams, "cloud provider id cannot be empty")
	}
	if strategy.RegionId == 0 {
		return nil, bizerr.New(bizerr.ErrCodeParams, "region id cannot be empty")
	}
	if strategy.GetServicePlan() == globalcommonv1.ServicePlan_SERVICE_PLAN_UNSPECIFIED {
		return nil, bizerr.New(bizerr.ErrCodeParams, "service plan cannot be empty")
	}
	return &schema.ResourcePoolStrategy{
		Model: gorm.Model{
			ID: uint(strategy.Id),
		},
		OrganizationId:        strategy.OrganizationId,
		DefaultResourcePoolId: strategy.DefaultResourcePoolId,
		ProviderId:            strategy.CloudProviderId,
		RegionId:              strategy.RegionId,
		ServicePlan:           strategy.GetServicePlan().String(),
	}, nil
}

func modelToEnStrategy(model *schema.ResourcePoolStrategy) (*entity.ResourcePoolStrategy, bizerr.BizError) {
	if model == nil {
		return &entity.ResourcePoolStrategy{}, nil
	}
	en := &entity.ResourcePoolStrategy{
		ResourcePoolStrategy: &globalngv1.ResourcePoolStrategy{
			Id:                    uint64(model.ID),
			OrganizationId:        model.OrganizationId,
			DefaultResourcePoolId: model.DefaultResourcePoolId,
			CreateTime:            timestamppb.New(model.CreatedAt),
			UpdateTime:            timestamppb.New(model.UpdatedAt),
		},
		RegionId:        uint64(model.Region.ID),
		CloudProviderId: uint64(model.Provider.ID),
	}

	servicePlan, err := enum.FromString[globalcommonv1.ServicePlan](model.ServicePlan)
	if err != nil {
		return nil, bizerr.WrapError(err, "failed to convert service plan")
	}
	en.ServicePlan = globalcommonv1.ServicePlan(servicePlan)

	provider, err := enum.FromString[globalcommonv1.CloudProvider_Name](model.Provider.Name)
	if err != nil {
		return nil, bizerr.WrapError(err, "failed to convert cloud provider")
	}
	en.ProviderId = globalcommonv1.CloudProvider_Name(provider)
	regionId, err := enum.RegionFromString(model.Provider.Name, model.Region.RegionName)
	if err != nil || regionId == globalcommonv1.Region_NAME_UNSPECIFIED {
		return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "invalid region: %s", model.Region.RegionName)
	}
	en.ResourcePoolStrategy.RegionId = regionId

	return en, nil
}
