package region

import (
	"context"

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

	globalcommonv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/common/v1"
	globalserverlessv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/serverless/v1"
	"github.com/tidbcloud/tidb-management-service/serverless-global-service/domain/entity"
	"github.com/tidbcloud/tidb-management-service/serverless-global-service/domain/repository"
	"github.com/tidbcloud/tidb-management-service/serverless-global-service/domain/service/cvt"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
	"github.com/tidbcloud/tidb-management-service/utilities/enum"
)

const (
	defaultRegionEngineType = globalcommonv1.Region_MYSQL_COMPATIBLE
	pgRegionEngineType      = globalcommonv1.Region_POSTGRESQL_COMPATIBLE
)

func (s *regionServiceImpl) RegisterRegion(
	ctx context.Context,
	req *globalserverlessv1.RegionServiceRegisterRequest,
) (*globalcommonv1.Region, error) {
	ctx, span := tracer.Start(ctx, "Domain.RegisterRegion")
	defer span.End()
	normalizeLegacyRegionFromWhiteLists(req.GetRegion())
	normalizeRegionEngineAndSys9(req.GetRegion())
	if req.GetRegion().GetServicePlan() == globalcommonv1.ServicePlan_SERVICE_PLAN_UNSPECIFIED {
		return nil, bizerr.New(bizerr.ErrCodeRecordNotFound, "service_plan parameter is required")
	}
	if err := validateRegionCapability(req.GetRegion()); err != nil {
		return nil, err
	}

	var region *globalcommonv1.Region
	if err := s.transaction.Transaction(ctx, func(ctx context.Context) error {
		p, err := s.providerRepo.LoadByName(ctx, req.GetRegion().GetCloudProviderId())
		if err != nil {
			return bizerr.New(bizerr.ErrCodeUnknown, "cloud provider not found: %w", err)
		}

		if p.State != globalcommonv1.CloudProvider_ACTIVE {
			return bizerr.New(bizerr.ErrCodeParams, "cloud provider is not active")
		}

		if req.GetRegion().GetEngineType() != pgRegionEngineType {
			resp, err := s.regionalSvc.Describe(ctx, &entity.Region{
				ServiceAddress: req.GetRegion().GetServiceAddress(),
			})
			if err != nil {
				tilog.Error(ctx, "describe region failed", zap.String("address", req.GetRegion().GetServiceAddress()), zap.Error(err))
				return bizerr.New(bizerr.ErrCodeParams, "describe region failed")
			}
			providerName := cvt.CloudProviderEnum2Name(resp.CloudProvider)
			if providerName != req.GetRegion().GetCloudProviderId() {
				return bizerr.New(bizerr.ErrCodeParams, "cloud provider not match")
			}
		}

		region, err = s.regionRepo.Save(ctx, req.GetRegion())
		if err != nil {
			return bizerr.New(bizerr.ErrCodeUnknown, "save region failed: %w", err)
		}
		return nil
	}); err != nil {
		return nil, bizerr.WrapError(err, "register region failed")
	}

	return region, nil
}

func (s *regionServiceImpl) UpdateRegion(
	ctx context.Context,
	req *globalserverlessv1.RegionServiceUpdateRequest,
) (*globalcommonv1.Region, error) {
	ctx, span := tracer.Start(ctx, "Domain.UpdateRegion")
	defer span.End()
	normalizeLegacyRegionFromWhiteLists(req.GetRegion())
	normalizeRegionEngineAndSys9(req.GetRegion())
	if req.GetRegion().GetServicePlan() == globalcommonv1.ServicePlan_SERVICE_PLAN_UNSPECIFIED {
		return nil, bizerr.New(bizerr.ErrCodeRecordNotFound, "service_plan parameter is required")
	}
	if err := validateRegionCapability(req.GetRegion()); err != nil {
		return nil, err
	}

	filter := &repository.CloudRegionFilter{
		CloudProviderNames: []globalcommonv1.CloudProvider_Name{req.GetRegion().GetCloudProviderId()},
		Names:              []globalcommonv1.Region_Name{req.GetRegion().GetId()},
		ServicePlans:       []globalcommonv1.ServicePlan{req.GetRegion().GetServicePlan()},
		EngineTypes:        []string{req.GetRegion().GetEngineType().String()},
	}
	var region *globalcommonv1.Region
	if err := s.transaction.Transaction(ctx, func(ctx context.Context) error {
		if req.GetRegion().GetEngineType() != pgRegionEngineType {
			_, err := s.regionalSvc.Describe(ctx, &entity.Region{
				ServiceAddress: req.GetRegion().GetServiceAddress(),
			})
			if err != nil {
				tilog.Error(ctx, "describe region failed", zap.String("address", req.GetRegion().GetServiceAddress()), zap.Error(err))
				return bizerr.New(bizerr.ErrCodeParams, "describe region failed")
			}
		}

		regions, err := s.regionRepo.LoadRegions(ctx, filter)
		if err != nil {
			return bizerr.New(bizerr.ErrCodeUnknown, "load region failed:  %w", err)
		}

		if len(regions) != 1 {
			return bizerr.New(bizerr.ErrCodeRecordNotFound, "region %s not found", req.GetRegion().GetId())
		}

		region = regions[0]

		if region.CloudProviderId != req.GetRegion().GetCloudProviderId() {
			return bizerr.New(bizerr.ErrCodeParams, "cloud provider cannot be changed")
		}

		region.DisplayName = req.GetRegion().GetDisplayName()
		region.ServiceAddress = req.GetRegion().GetServiceAddress()
		region.OpenedTenantIds = req.GetRegion().GetOpenedTenantIds()
		region.DefaultBackupTime = req.GetRegion().GetDefaultBackupTime()
		region.IsPublic = req.GetRegion().GetIsPublic()
		region.State = req.GetRegion().GetState()
		region.ServicePlan = req.GetRegion().GetServicePlan()
		region.EngineType = req.GetRegion().GetEngineType()
		region.Sys9Config = req.GetRegion().GetSys9Config()

		region, err = s.regionRepo.Save(ctx, region)
		if err != nil {
			return bizerr.New(bizerr.ErrCodeUnknown, "save region failed: %w", err)
		}
		return nil
	}); err != nil {
		return nil, bizerr.WrapError(err, "update region failed")
	}

	return region, nil
}

func (s *regionServiceImpl) ListRegions(
	ctx context.Context,
	req *globalserverlessv1.RegionServiceListRequest,
) (*globalserverlessv1.RegionServiceListResponse, error) {
	ctx, span := tracer.Start(ctx, "Domain.ListRegions")
	defer span.End()

	regions, err := s.regionRepo.LoadRegions(ctx, &repository.CloudRegionFilter{
		CloudProviderNames: req.GetCloudProviderIds(),
		EngineTypes:        listReqEngineTypes(req.GetEngineType()),
	})

	if err != nil {
		return nil, err
	}

	if req.GetOrganizationId() == 0 &&
		len(req.GetCloudProviderIds()) == 0 &&
		req.GetServicePlan() == globalcommonv1.ServicePlan_SERVICE_PLAN_UNSPECIFIED {
		return &globalserverlessv1.RegionServiceListResponse{
			Regions: regions,
			Total:   uint32(len(regions)),
		}, nil
	}

	filteredRegions := make([]*globalcommonv1.Region, 0)
	for _, region := range regions {
		// filter by service plan
		if req.GetServicePlan() != globalcommonv1.ServicePlan_SERVICE_PLAN_UNSPECIFIED &&
			req.GetServicePlan() != region.ServicePlan {
			continue
		}
		// filter by org with private region
		if !region.GetIsPublic() &&
			!lo.ContainsBy(
				region.GetOpenedTenantIds(),
				func(i uint64) bool { return i == req.GetOrganizationId() },
			) {
			continue
		}
		filteredRegions = append(filteredRegions, region)
	}
	return &globalserverlessv1.RegionServiceListResponse{
		Regions: filteredRegions,
		Total:   uint32(len(filteredRegions)),
	}, nil
}

func (s *regionServiceImpl) GetRegion(
	ctx context.Context,
	req *globalserverlessv1.RegionServiceGetRequest,
) (*globalcommonv1.Region, error) {
	ctx, span := tracer.Start(ctx, "Domain.GetRegion")
	defer span.End()

	filter := &repository.CloudRegionFilter{
		CloudProviderNames: []globalcommonv1.CloudProvider_Name{req.GetCloudProviderId()},
		Names:              []globalcommonv1.Region_Name{req.GetRegionId()},
		ServicePlans:       []globalcommonv1.ServicePlan{req.GetServicePlan()},
		EngineTypes:        listReqEngineTypes(req.GetEngineType()),
	}

	regions, err := s.regionRepo.LoadRegions(ctx, filter)
	if err != nil {
		return nil, err
	}
	if len(regions) != 1 {
		return nil, bizerr.New(bizerr.ErrCodeRecordNotFound, "region not found")
	}
	return regions[0], nil
}

func (s *regionServiceImpl) DeleteRegion(
	ctx context.Context,
	req *globalserverlessv1.RegionServiceDeleteRequest,
) error {
	ctx, span := tracer.Start(ctx, "Domain.DeleteRegion")
	defer span.End()

	filter := &repository.CloudRegionFilter{
		CloudProviderNames: []globalcommonv1.CloudProvider_Name{req.GetCloudProviderId()},
		Names:              []globalcommonv1.Region_Name{req.GetRegionId()},
		ServicePlans:       []globalcommonv1.ServicePlan{req.GetServicePlan()},
		EngineTypes:        listReqEngineTypes(req.GetEngineType()),
	}

	regions, err := s.regionRepo.LoadRegions(ctx, filter)
	if err != nil {
		return err
	}
	if len(regions) != 1 {
		return bizerr.New(bizerr.ErrCodeRecordNotFound, "region not found")
	}

	if regions[0].State != globalcommonv1.Region_INACTIVE {
		return bizerr.New(bizerr.ErrCodeParams, "region is not inactive")
	}

	// check cluster
	count, err := s.clusterRepo.Count(ctx, &repository.ClusterQueryParameter{
		RegionNames:        []globalcommonv1.Region_Name{req.GetRegionId()},
		CloudProviderNames: []globalcommonv1.CloudProvider_Name{req.GetCloudProviderId()},
		EngineTypes:        []string{regions[0].GetEngineType().String()},
	})

	if err != nil {
		return err
	}

	if count > 0 {
		return bizerr.New(bizerr.ErrCodeParams, "region has cluster")
	}

	region := regions[0]
	err = s.regionRepo.Delete(ctx, region)
	if err != nil {
		return bizerr.New(bizerr.ErrCodeUnknown, "delete region failed: %w", err)
	}

	return nil
}

func normalizeLegacyRegionFromWhiteLists(region *globalcommonv1.Region) {
	if region == nil || len(region.GetRegionWhiteLists()) == 0 {
		return
	}
	for _, wl := range region.GetRegionWhiteLists() {
		if region.GetServicePlan() != globalcommonv1.ServicePlan_SERVICE_PLAN_UNSPECIFIED &&
			wl.GetServicePlan() != region.GetServicePlan() {
			continue
		}
		region.ServicePlan = wl.GetServicePlan()
		region.IsPublic = wl.GetIsPublic()
		region.OpenedTenantIds = append([]uint64(nil), wl.GetOpenedTenantIds()...)
		return
	}
	wl := region.GetRegionWhiteLists()[0]
	region.ServicePlan = wl.GetServicePlan()
	region.IsPublic = wl.GetIsPublic()
	region.OpenedTenantIds = append([]uint64(nil), wl.GetOpenedTenantIds()...)
}

func normalizeRegionEngineAndSys9(region *globalcommonv1.Region) {
	if region == nil {
		return
	}
	if region.GetEngineType() == globalcommonv1.Region_ENGINE_TYPE_UNSPECIFIED {
		region.EngineType = defaultRegionEngineType
	}
}

func listReqEngineTypes(engineType string) []string {
	if engineType == "" {
		return []string{defaultRegionEngineType.String()}
	}
	engineTypeEnum, err := enum.FromString[globalcommonv1.Region_EngineType](engineType)
	if err != nil {
		return []string{defaultRegionEngineType.String()}
	}
	return []string{globalcommonv1.Region_EngineType(engineTypeEnum).String()}
}

func validateRegionCapability(region *globalcommonv1.Region) error {
	if region == nil {
		return bizerr.New(bizerr.ErrCodeParams, "region parameter is required")
	}
	if region.GetServicePlan() != globalcommonv1.ServicePlan_Starter {
		return nil
	}
	if region.GetEngineType() != pgRegionEngineType {
		return nil
	}
	if region.GetSys9Config() == nil {
		return bizerr.New(bizerr.ErrCodeParams, "sys9_config is required for starter pg region")
	}
	if region.GetServiceAddress() == "" {
		return bizerr.New(bizerr.ErrCodeParams, "service_address is required for starter pg region")
	}
	if region.GetSys9Config().GetOemProfileRef() == "" {
		return bizerr.New(bizerr.ErrCodeParams, "sys9_config.oem_profile_ref is required for starter pg region")
	}
	return nil
}
