package resourcepool

import (
	"context"
	"errors"

	"github.com/samber/lo"
	"github.com/tidbcloud/pkgv2/db"
	"go.opentelemetry.io/otel"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"

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

var strategyTracer = otel.Tracer("resourcepoolstrategy_repo_impl")

type resourcePoolStrategyRepoImpl struct {
	provider db.Provider
}

// NewResourcePoolStrategyRepo creates a new ResourcePoolStrategy repository instance.
func NewResourcePoolStrategyRepo(provider db.Provider) resourcepool.StrategyRepo {
	return &resourcePoolStrategyRepoImpl{
		provider: provider,
	}
}

// Create inserts a new ResourcePoolStrategy record into the database.
func (i *resourcePoolStrategyRepoImpl) Create(ctx context.Context, strategy *entity.ResourcePoolStrategy) bizerr.BizError {
	ctx, span := strategyTracer.Start(ctx, "ResourcePoolStrategyRepo.Create")
	defer span.End()
	if strategy == nil {
		return bizerr.New(bizerr.ErrCodeUnknown, "resourcePoolStrategy entity is nil")
	}

	wdb := i.provider.WriteDB(ctx)

	var providerModel schema.Provider
	if err := wdb.Take(&providerModel, "`id` = ?", strategy.CloudProviderId).Error; err != nil {
		return bizerr.WrapGormError(err, "failed to query provider")
	}
	var regionModel schema.Region
	if err := wdb.Take(&regionModel, "`id` = ?", strategy.RegionId).Error; err != nil {
		return bizerr.WrapGormError(err, "failed to query region")
	}

	schemaStrategy, err := enToModelStrategy(strategy)
	if err != nil {
		return err
	}

	dbErr := wdb.Create(schemaStrategy).Error
	if dbErr != nil {
		return bizerr.WrapGormError(dbErr, "failed to create resource pool strategy")
	}
	strategy.Id = uint64(schemaStrategy.ID)

	return nil
}

// Save updates an existing ResourcePoolStrategy record in the database.
func (i *resourcePoolStrategyRepoImpl) Save(ctx context.Context, strategy *entity.ResourcePoolStrategy) bizerr.BizError {
	ctx, span := strategyTracer.Start(ctx, "ResourcePoolStrategyRepo.Save")
	defer span.End()

	if strategy == nil || strategy.Id == 0 {
		return bizerr.New(bizerr.ErrCodeUnknown, "resourcePoolStrategy entity is nil or ID is empty")
	}
	wdb := i.provider.WriteDB(ctx)

	var providerModel schema.Provider
	if err := wdb.Take(&providerModel, "`id` = ?", strategy.CloudProviderId).Error; err != nil {
		return bizerr.WrapGormError(err, "failed to query provider")
	}

	schemaStrategy, err := enToModelStrategy(strategy)
	if err != nil {
		return err
	}

	dbErr := wdb.Omit(clause.Associations, "created_at").Save(schemaStrategy).Error
	if dbErr != nil {
		return bizerr.WrapGormError(dbErr, "failed to save resource pool strategy")
	}

	return nil
}

func (i *resourcePoolStrategyRepoImpl) SwitchDefaultResourcePool(
	ctx context.Context,
	strategyId uint64,
	oldPoolId uint64,
	newPoolId uint64,
) (bool, bizerr.BizError) {
	ctx, span := strategyTracer.Start(ctx, "ResourcePoolStrategyRepo.SwitchDefaultResourcePool")
	defer span.End()

	if strategyId == 0 {
		return false, bizerr.New(bizerr.ErrCodeParams, "strategy id is empty")
	}
	if oldPoolId == 0 || newPoolId == 0 {
		return false, bizerr.New(bizerr.ErrCodeParams, "old or new pool id is empty")
	}
	if oldPoolId == newPoolId {
		return false, nil
	}

	wdb := i.provider.WriteDB(ctx)
	result := wdb.Model(&schema.ResourcePoolStrategy{}).
		Where("`id` = ? AND `default_resource_pool_id` = ?", strategyId, oldPoolId).
		Update("default_resource_pool_id", newPoolId)
	if result.Error != nil {
		return false, bizerr.WrapGormError(result.Error, "failed to switch default resource pool")
	}

	return result.RowsAffected > 0, nil
}

// Load retrieves a ResourcePoolStrategy by ID, including its associated Provider and Region.
func (i *resourcePoolStrategyRepoImpl) Load(ctx context.Context, id uint64) (*entity.ResourcePoolStrategy, bizerr.BizError) {
	ctx, span := strategyTracer.Start(ctx, "ResourcePoolStrategyRepo.Load")
	defer span.End()

	if id == 0 {
		return nil, bizerr.New(bizerr.ErrCodeUnknown, "resource pool strategy id is empty")
	}

	var schemaStrategy schema.ResourcePoolStrategy
	rdb := i.provider.ReadDB(ctx)

	// Eager load Provider and Region using INNER JOINs.
	rdb = rdb.Model(&schema.ResourcePoolStrategy{}).InnerJoins("Provider").InnerJoins("Region").
		Where("`resource_pool_strategies`.`id` = ?", id)

	// Default GORM behavior excludes soft-deleted records.
	if err := rdb.Take(&schemaStrategy).Error; err != nil {
		return nil, bizerr.WrapGormError(err, "failed to get resource pool strategy")
	}

	return modelToEnStrategy(&schemaStrategy)
}

// Delete soft-deletes a ResourcePoolStrategy record by ID.
func (i *resourcePoolStrategyRepoImpl) Delete(ctx context.Context, id uint64) bizerr.BizError {
	ctx, span := strategyTracer.Start(ctx, "ResourcePoolStrategyRepo.Delete")
	defer span.End()

	if id == 0 {
		return bizerr.New(bizerr.ErrCodeUnknown, "id is empty")
	}

	wdb := i.provider.WriteDB(ctx)

	if err := wdb.Where("`id` = ?", id).Delete(&schema.ResourcePoolStrategy{}).Error; err != nil {
		return bizerr.WrapGormError(err, "delete resource pool strategy")
	}
	return nil
}

// Query retrieves ResourcePoolStrategy records based on the provided StrategyFilter.
func (i *resourcePoolStrategyRepoImpl) Query(
	ctx context.Context,
	filter *resourcepool.StrategyFilter,
) ([]*entity.ResourcePoolStrategy, bizerr.BizError) {
	ctx, span := strategyTracer.Start(ctx, "ResourcePoolStrategyRepo.Query")
	defer span.End()

	rdb := i.provider.ReadDB(ctx)

	var schemaStrategies []*schema.ResourcePoolStrategy

	tx := rdb.Model(&schema.ResourcePoolStrategy{}).InnerJoins("Provider").InnerJoins("Region")

	if len(filter.Ids) > 0 {
		tx = tx.Where("`resource_pool_strategies`.`id` IN ?", filter.Ids)
	}

	if len(filter.CloudProviderNames) > 0 {
		cloudProviders := lo.Map(
			filter.CloudProviderNames,
			func(cloudProvider entity.CloudProviderName, index int) string {
				return cloudProvider.String()
			},
		)
		tx = tx.Where("`Provider`.`name` IN ?", cloudProviders)
	}

	if len(filter.RegionNames) > 0 {
		tx = tx.Where(
			"`Region`.`region_name` IN ?",
			lo.Map(filter.RegionNames, func(region entity.RegionName, index int) string {
				return region.String()
			}),
		)
	}

	if len(filter.OrganizationIds) > 0 {
		tx = tx.Where("`resource_pool_strategies`.`organization_id` in ?", filter.OrganizationIds)
	}
	if len(filter.ServicePlans) > 0 {
		servicePlans := lo.FilterMap(
			filter.ServicePlans,
			func(servicePlan entity.ServicePlan, _ int) (string, bool) {
				if servicePlan == globalcommonv1.ServicePlan_SERVICE_PLAN_UNSPECIFIED {
					return "", false
				}
				return servicePlan.String(), true
			},
		)
		if len(servicePlans) > 0 {
			tx = tx.Where("`resource_pool_strategies`.`service_plan` IN ?", servicePlans)
		}
	}
	if len(filter.DefaultResourcePoolIds) > 0 {
		tx = tx.Where("`resource_pool_strategies`.`default_resource_pool_id` IN ?", filter.DefaultResourcePoolIds)
	}

	if err := tx.Find(&schemaStrategies).Error; err != nil {
		return nil, bizerr.WrapGormError(err, "query resource pool strategy failed")
	}

	var strategies []*entity.ResourcePoolStrategy
	for _, schemaStrategy := range schemaStrategies {
		en, err := modelToEnStrategy(schemaStrategy)
		if err != nil {
			return nil, err
		}
		strategies = append(strategies, en)
	}

	return strategies, nil
}

func (i *resourcePoolStrategyRepoImpl) GetByOrgProviderRegion(ctx context.Context,
	orgId uint64,
	provider entity.CloudProviderName,
	region entity.RegionName,
	servicePlan entity.ServicePlan) (*entity.ResourcePoolStrategy, bizerr.BizError) {
	ctx, span := strategyTracer.Start(ctx, "ResourcePoolStrategyRepo.GetByOrgProviderRegion")
	defer span.End()

	if servicePlan == globalcommonv1.ServicePlan_SERVICE_PLAN_UNSPECIFIED {
		return nil, bizerr.New(bizerr.ErrCodeParams, "service plan is required")
	}

	rdb := i.provider.ReadDB(ctx)

	var schemaStrategy schema.ResourcePoolStrategy
	tx := rdb.Model(&schema.ResourcePoolStrategy{}).InnerJoins("Provider").InnerJoins("Region")

	tx = tx.Where("`resource_pool_strategies`.`organization_id` = ?", orgId)

	tx = tx.Where("`Provider`.`name` = ?", provider.String())

	tx = tx.Where("`Region`.`region_name` = ?", region.String())

	tx = tx.Where("`resource_pool_strategies`.`service_plan` = ?", servicePlan.String())

	if err := tx.Find(&schemaStrategy).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return &entity.ResourcePoolStrategy{}, nil
		}
		return nil, bizerr.WrapGormError(err, "get resource pool strategy by unique key failed")
	}

	if schemaStrategy.ID == 0 {
		return &entity.ResourcePoolStrategy{}, nil
	}

	en, err := modelToEnStrategy(&schemaStrategy)
	if err != nil {
		return nil, err
	}

	return en, nil
}
