// Package shop_series_grant 提供代理系列授权的业务逻辑服务
// 包含授权创建、查询、更新、套餐管理、删除等功能
package shop_series_grant

import (
	"context"
	"time"

	"github.com/break/junhong_cmp_fiber/internal/model"
	"github.com/break/junhong_cmp_fiber/internal/model/dto"
	packagepkg "github.com/break/junhong_cmp_fiber/internal/service/package"
	"github.com/break/junhong_cmp_fiber/internal/store"
	"github.com/break/junhong_cmp_fiber/internal/store/postgres"
	"github.com/break/junhong_cmp_fiber/pkg/constants"
	"github.com/break/junhong_cmp_fiber/pkg/errors"
	"github.com/break/junhong_cmp_fiber/pkg/middleware"
	"go.uber.org/zap"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"
)

// Service 代理系列授权业务服务
type Service struct {
	db                                     *gorm.DB
	shopSeriesAllocationStore              *postgres.ShopSeriesAllocationStore
	shopPackageAllocationStore             *postgres.ShopPackageAllocationStore
	shopPackageAllocationPriceHistoryStore *postgres.ShopPackageAllocationPriceHistoryStore
	shopStore                              *postgres.ShopStore
	packageStore                           *postgres.PackageStore
	packageSeriesStore                     *postgres.PackageSeriesStore
	logger                                 *zap.Logger
}

// initialGrantPackage 保存通过创建前校验的套餐及其请求价格。
type initialGrantPackage struct {
	item         dto.GrantPackageItem
	packageModel *model.Package
}

// New 创建代理系列授权服务实例
func New(
	db *gorm.DB,
	shopSeriesAllocationStore *postgres.ShopSeriesAllocationStore,
	shopPackageAllocationStore *postgres.ShopPackageAllocationStore,
	shopPackageAllocationPriceHistoryStore *postgres.ShopPackageAllocationPriceHistoryStore,
	shopStore *postgres.ShopStore,
	packageStore *postgres.PackageStore,
	packageSeriesStore *postgres.PackageSeriesStore,
	logger *zap.Logger,
) *Service {
	return &Service{
		db:                                     db,
		shopSeriesAllocationStore:              shopSeriesAllocationStore,
		shopPackageAllocationStore:             shopPackageAllocationStore,
		shopPackageAllocationPriceHistoryStore: shopPackageAllocationPriceHistoryStore,
		shopStore:                              shopStore,
		packageStore:                           packageStore,
		packageSeriesStore:                     packageSeriesStore,
		logger:                                 logger,
	}
}

// validateInitialGrantPackageItems 校验首次授权套餐的数量、价格和批内唯一性。
func validateInitialGrantPackageItems(items []dto.GrantPackageItem) ([]uint, error) {
	if len(items) == 0 || len(items) > constants.ShopSeriesGrantMaxPackages {
		return nil, errors.New(errors.CodeInvalidParam, "初始授权套餐数量必须为1到100项")
	}

	packageIDs := make([]uint, 0, len(items))
	seen := make(map[uint]struct{}, len(items))
	for _, item := range items {
		if item.PackageID == 0 || item.CostPrice == nil || *item.CostPrice < 0 {
			return nil, errors.New(errors.CodeInvalidParam, "套餐ID和成本价参数无效")
		}
		if _, exists := seen[item.PackageID]; exists {
			return nil, errors.New(errors.CodeInvalidParam, "初始授权套餐ID不能重复")
		}
		seen[item.PackageID] = struct{}{}
		packageIDs = append(packageIDs, item.PackageID)
	}
	return packageIDs, nil
}

// validateInitialGrantPackages 批量校验首次授权的套餐归属、授权链和成本价边界。
func (s *Service) validateInitialGrantPackages(ctx context.Context, tx *gorm.DB, req *dto.CreateShopSeriesGrantRequest, allocatorShopID uint) ([]initialGrantPackage, error) {
	packageIDs, err := validateInitialGrantPackageItems(req.Packages)
	if err != nil {
		return nil, err
	}

	var packages []*model.Package
	if err := tx.WithContext(ctx).
		Clauses(clause.Locking{Strength: "UPDATE"}).
		Where("id IN ?", packageIDs).
		Find(&packages).Error; err != nil {
		return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询初始授权套餐失败")
	}
	if len(packages) != len(packageIDs) {
		return nil, errors.New(errors.CodeInvalidParam, "初始授权套餐不存在或已删除")
	}

	packageMap := make(map[uint]*model.Package, len(packages))
	for _, pkg := range packages {
		packageMap[pkg.ID] = pkg
	}

	parentCostMap := make(map[uint]int64, len(packageIDs))
	if allocatorShopID > 0 {
		var parentAllocations []*model.ShopPackageAllocation
		if err := tx.WithContext(ctx).
			Clauses(clause.Locking{Strength: "UPDATE"}).
			Where("shop_id = ? AND package_id IN ? AND status = ?", allocatorShopID, packageIDs, constants.StatusEnabled).
			Find(&parentAllocations).Error; err != nil {
			return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询上级套餐授权失败")
		}
		if len(parentAllocations) != len(packageIDs) {
			return nil, errors.New(errors.CodeForbidden, "无权限分配套餐")
		}
		for _, allocation := range parentAllocations {
			parentCostMap[allocation.PackageID] = allocation.CostPrice
		}
	}

	validated := make([]initialGrantPackage, 0, len(req.Packages))
	for _, item := range req.Packages {
		pkg := packageMap[item.PackageID]
		if pkg.SeriesID != req.SeriesID {
			return nil, errors.New(errors.CodeInvalidParam, "套餐不属于该系列，无法添加到此授权")
		}
		if pkg.IsGift {
			return nil, errors.New(errors.CodeForbidden, "赠送套餐不能分配给代理")
		}

		minimumCost := pkg.CostPrice
		if allocatorShopID > 0 {
			minimumCost = parentCostMap[item.PackageID]
		}
		if *item.CostPrice < minimumCost {
			return nil, errors.New(errors.CodeInvalidParam, "授权成本价不能低于当前上级成本价")
		}
		validated = append(validated, initialGrantPackage{item: item, packageModel: pkg})
	}
	return validated, nil
}

// getParentCeilingFixed 查询固定模式佣金天花板
// allocatorShopID=0 表示平台分配，天花板为 PackageSeries.commission_amount
// allocatorShopID>0 表示代理分配，天花板为分配者自身的 ShopSeriesAllocation.one_time_commission_amount
func (s *Service) getParentCeilingFixed(ctx context.Context, allocatorShopID uint, seriesID uint, config *model.OneTimeCommissionConfig) (int64, error) {
	if allocatorShopID == 0 {
		// 平台分配：天花板为套餐系列全局佣金金额
		return config.CommissionAmount, nil
	}
	// 代理分配：天花板为分配者自身拥有的授权金额
	allocatorAllocation, err := s.shopSeriesAllocationStore.GetByShopAndSeries(ctx, allocatorShopID, seriesID)
	if err != nil {
		return 0, errors.Wrap(errors.CodeNotFound, err, "分配者无此系列授权")
	}
	return allocatorAllocation.OneTimeCommissionAmount, nil
}

// getParentCeilingTiered 查询梯度模式各档位天花板（map: threshold→amount）
// allocatorShopID=0 → 读 PackageSeries 全局 Tiers 中各 threshold 的 amount
// allocatorShopID>0 → 读分配者自身 ShopSeriesAllocation.commission_tiers_json
func (s *Service) getParentCeilingTiered(ctx context.Context, allocatorShopID uint, seriesID uint, globalTiers []model.OneTimeCommissionTier) (map[int64]int64, error) {
	ceilingMap := make(map[int64]int64)

	if allocatorShopID == 0 {
		// 平台分配：天花板来自套餐系列全局配置
		for _, t := range globalTiers {
			ceilingMap[t.Threshold] = t.Amount
		}
		return ceilingMap, nil
	}

	// 代理分配：天花板来自分配者的专属阶梯
	allocatorAllocation, err := s.shopSeriesAllocationStore.GetByShopAndSeries(ctx, allocatorShopID, seriesID)
	if err != nil {
		return nil, errors.Wrap(errors.CodeNotFound, err, "分配者无此系列授权")
	}
	agentTiers, err := allocatorAllocation.GetCommissionTiers()
	if err != nil {
		return nil, errors.Wrap(errors.CodeInternalError, err, "解析分配者阶梯佣金失败")
	}
	for _, t := range agentTiers {
		ceilingMap[t.Threshold] = t.Amount
	}
	return ceilingMap, nil
}

// buildGrantResponse 构建授权详情响应
func (s *Service) buildGrantResponse(ctx context.Context, allocation *model.ShopSeriesAllocation, series *model.PackageSeries, config *model.OneTimeCommissionConfig) (*dto.ShopSeriesGrantResponse, error) {
	resp := &dto.ShopSeriesGrantResponse{
		ID:              allocation.ID,
		ShopID:          allocation.ShopID,
		SeriesID:        allocation.SeriesID,
		SeriesName:      series.SeriesName,
		SeriesCode:      series.SeriesCode,
		CommissionType:  config.CommissionType,
		AllocatorShopID: allocation.AllocatorShopID,
		Status:          allocation.Status,
		CreatedAt:       allocation.CreatedAt.Format(time.DateTime),
		UpdatedAt:       allocation.UpdatedAt.Format(time.DateTime),
	}

	// 查询店铺名称
	shop, err := s.shopStore.GetByID(ctx, allocation.ShopID)
	if err == nil {
		resp.ShopName = shop.ShopName
	}

	// 查询分配者名称
	if allocation.AllocatorShopID > 0 {
		allocatorShop, err := s.shopStore.GetByID(ctx, allocation.AllocatorShopID)
		if err == nil {
			resp.AllocatorShopName = allocatorShop.ShopName
		}
	} else {
		resp.AllocatorShopName = "平台"
	}

	// 强充有效状态：first_recharge 或平台已启用 accumulated_recharge 强充时，锁定不可改
	forceRechargeLocked := config.TriggerType == model.OneTimeCommissionTriggerFirstRecharge || config.EnableForceRecharge
	resp.ForceRechargeLocked = forceRechargeLocked
	if forceRechargeLocked {
		// 锁定时强充实际生效，金额取套餐系列配置值（allocation 字段为 0 不能使用）
		resp.ForceRechargeEnabled = true
		resp.ForceRechargeAmount = config.ForceAmount
	} else {
		resp.ForceRechargeEnabled = allocation.EnableForceRecharge
		resp.ForceRechargeAmount = allocation.ForceRechargeAmount
	}

	// 固定模式
	if config.CommissionType == "fixed" {
		resp.OneTimeCommissionAmount = allocation.OneTimeCommissionAmount
		resp.CommissionTiers = []dto.GrantCommissionTierItem{}
	} else {
		// 梯度模式：将代理专属金额与全局 operator 合并
		resp.OneTimeCommissionAmount = 0
		agentTiers, err := allocation.GetCommissionTiers()
		if err != nil {
			return nil, errors.Wrap(errors.CodeInternalError, err, "解析代理阶梯佣金失败")
		}
		// 按 threshold 索引代理金额
		agentAmountMap := make(map[int64]int64)
		for _, t := range agentTiers {
			agentAmountMap[t.Threshold] = t.Amount
		}
		// 合并全局 operator 和代理 amount
		tiers := make([]dto.GrantCommissionTierItem, 0, len(config.Tiers))
		for _, globalTier := range config.Tiers {
			tiers = append(tiers, dto.GrantCommissionTierItem{
				Operator:  globalTier.Operator,
				Dimension: globalTier.Dimension,
				StatScope: globalTier.StatScope,
				Threshold: globalTier.Threshold,
				Amount:    agentAmountMap[globalTier.Threshold],
			})
		}
		resp.CommissionTiers = tiers
	}

	// 查询已授权套餐列表
	pkgAllocations, err := s.shopPackageAllocationStore.GetBySeriesAllocationID(ctx, allocation.ID)
	if err != nil {
		return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询套餐分配失败")
	}
	packages := make([]dto.ShopSeriesGrantPackageItem, 0, len(pkgAllocations))
	for _, pa := range pkgAllocations {
		pkg, pkgErr := s.packageStore.GetByID(ctx, pa.PackageID)
		if pkgErr != nil {
			continue
		}
		if pkg.IsGift {
			continue
		}
		effectiveBase := packagepkg.EffectiveExpiryBase(pkg, pa)
		packages = append(packages, dto.ShopSeriesGrantPackageItem{
			PackageID:               pa.PackageID,
			PackageName:             pkg.PackageName,
			PackageCode:             pkg.PackageCode,
			CostPrice:               pa.CostPrice,
			ShelfStatus:             pa.ShelfStatus,
			Status:                  pa.Status,
			DefaultExpiryBase:       pkg.ExpiryBase,
			DefaultExpiryBaseName:   packagepkg.ExpiryBaseName(pkg.ExpiryBase),
			ExpiryBaseOverride:      pa.ExpiryBaseOverride,
			ExpiryBaseOverrideName:  packagepkg.ExpiryBaseOverrideName(pa.ExpiryBaseOverride),
			EffectiveExpiryBase:     effectiveBase,
			EffectiveExpiryBaseName: packagepkg.ExpiryBaseName(effectiveBase),
		})
	}
	resp.Packages = packages

	return resp, nil
}

// Create 创建系列授权
// POST /api/admin/shop-series-grants
func (s *Service) Create(ctx context.Context, req *dto.CreateShopSeriesGrantRequest) (*dto.ShopSeriesGrantResponse, error) {
	expiryBaseOverride, err := packagepkg.ValidateExpiryBaseOverride(req.ExpiryBaseOverride, req.ExpiryBaseOverrideSet)
	if err != nil {
		return nil, err
	}
	operatorID := middleware.GetUserIDFromContext(ctx)
	operatorShopID := middleware.GetShopIDFromContext(ctx)
	operatorType := middleware.GetUserTypeFromContext(ctx)

	// 1. 查询套餐系列，解析一次性佣金配置（可选）
	// 系列授权同时作为套餐销售权限的入口记录，未启用一次性佣金的系列也可创建授权，佣金金额默认为 0
	series, err := s.packageSeriesStore.GetByID(ctx, req.SeriesID)
	if err != nil {
		return nil, errors.New(errors.CodeNotFound, "套餐系列不存在")
	}
	config, _ := series.GetOneTimeCommissionConfig()

	// 1.5 先校验管理范围，避免通过错误差异探测其他店铺。
	if err := middleware.CanManageShop(ctx, req.ShopID); err != nil {
		return nil, errors.New(errors.CodeForbidden, "无权限操作该资源或资源不存在")
	}
	targetShop, err := s.shopStore.GetByID(ctx, req.ShopID)
	if err != nil {
		return nil, errors.New(errors.CodeForbidden, "无权限操作该资源或资源不存在")
	}

	// 2. 检查重复授权
	exists, err := s.shopSeriesAllocationStore.ExistsByShopAndSeries(ctx, req.ShopID, req.SeriesID)
	if err != nil {
		return nil, errors.Wrap(errors.CodeDatabaseError, err, "检查授权重复失败")
	}
	if exists {
		return nil, errors.New(errors.CodeConflict, "该代理已存在此系列授权")
	}

	// 3. 确定 allocatorShopID（代理操作者必须自己有授权才能向下分配）
	var allocatorShopID uint
	if operatorType == constants.UserTypeAgent {
		allocatorShopID = operatorShopID
		if allocatorShopID == 0 || targetShop.ParentID == nil || *targetShop.ParentID != allocatorShopID {
			return nil, errors.New(errors.CodeForbidden, "只能授权直属下级店铺")
		}
		var parentSeriesAllocation model.ShopSeriesAllocation
		if err := s.db.WithContext(ctx).
			Where("shop_id = ? AND series_id = ? AND status = ?", allocatorShopID, req.SeriesID, constants.StatusEnabled).
			First(&parentSeriesAllocation).Error; err != nil {
			return nil, errors.New(errors.CodeForbidden, "当前账号无此系列授权，无法向下分配")
		}
	}
	// 平台/超管 allocatorShopID = 0
	if operatorType != constants.UserTypeAgent && operatorType != constants.UserTypePlatform && operatorType != constants.UserTypeSuperAdmin {
		return nil, errors.New(errors.CodeForbidden, "无权限创建系列授权")
	}

	// 4. 参数验证：仅启用一次性佣金的系列才需要配置佣金金额
	allocation := &model.ShopSeriesAllocation{
		ShopID:              req.ShopID,
		SeriesID:            req.SeriesID,
		AllocatorShopID:     allocatorShopID,
		Status:              constants.StatusEnabled,
		CommissionTiersJSON: "[]",
	}
	allocation.Creator = operatorID
	allocation.Updater = operatorID

	if config != nil && config.Enable {
		if config.CommissionType == "fixed" {
			if req.OneTimeCommissionAmount == nil {
				return nil, errors.New(errors.CodeInvalidParam, "固定模式必须填写佣金金额")
			}
			ceiling, ceilErr := s.getParentCeilingFixed(ctx, allocatorShopID, req.SeriesID, config)
			if ceilErr != nil {
				return nil, ceilErr
			}
			if *req.OneTimeCommissionAmount > ceiling {
				return nil, errors.New(errors.CodeInvalidParam, "佣金金额不能超过上级天花板")
			}
			allocation.OneTimeCommissionAmount = *req.OneTimeCommissionAmount
		} else {
			// 梯度模式
			if len(req.CommissionTiers) == 0 {
				return nil, errors.New(errors.CodeInvalidParam, "梯度模式必须填写阶梯配置")
			}
			// 阶梯数量和 threshold 必须与全局完全一致
			if len(req.CommissionTiers) != len(config.Tiers) {
				return nil, errors.New(errors.CodeInvalidParam, "梯度阶梯数量与系列配置不一致")
			}
			ceilingMap, ceilErr := s.getParentCeilingTiered(ctx, allocatorShopID, req.SeriesID, config.Tiers)
			if ceilErr != nil {
				return nil, ceilErr
			}
			agentTiers := make([]model.AllocationCommissionTier, 0, len(req.CommissionTiers))
			for i, tier := range req.CommissionTiers {
				if tier.Threshold != config.Tiers[i].Threshold {
					return nil, errors.New(errors.CodeInvalidParam, "梯度阶梯 threshold 与系列配置不匹配")
				}
				ceiling, ok := ceilingMap[tier.Threshold]
				if !ok {
					ceiling = 0
				}
				if tier.Amount > ceiling {
					return nil, errors.New(errors.CodeInvalidParam, "某档位佣金金额超过上级天花板")
				}
				agentTiers = append(agentTiers, model.AllocationCommissionTier{
					Threshold: tier.Threshold,
					Amount:    tier.Amount,
				})
			}
			if err := allocation.SetCommissionTiers(agentTiers); err != nil {
				return nil, errors.Wrap(errors.CodeInternalError, err, "序列化阶梯佣金失败")
			}
		}

		// 5. 强充配置：first_recharge 或平台已开强充 → locked，忽略代理传入
		forceRechargeLocked := config.TriggerType == model.OneTimeCommissionTriggerFirstRecharge || config.EnableForceRecharge
		if !forceRechargeLocked && req.EnableForceRecharge != nil && *req.EnableForceRecharge {
			allocation.EnableForceRecharge = true
			if req.ForceRechargeAmount != nil {
				allocation.ForceRechargeAmount = *req.ForceRechargeAmount
			}
		}
	}

	// 6. 事务中创建 ShopSeriesAllocation + N 条 ShopPackageAllocation
	err = s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		var lockedTargetShop model.Shop
		if lockErr := tx.WithContext(ctx).
			Clauses(clause.Locking{Strength: "UPDATE"}).
			First(&lockedTargetShop, req.ShopID).Error; lockErr != nil {
			return errors.New(errors.CodeForbidden, "无权限操作该资源或资源不存在")
		}
		if allocatorShopID > 0 {
			if lockedTargetShop.ParentID == nil || *lockedTargetShop.ParentID != allocatorShopID {
				return errors.New(errors.CodeForbidden, "只能授权直属下级店铺")
			}
			var parentSeriesAllocation model.ShopSeriesAllocation
			if lockErr := tx.WithContext(ctx).
				Clauses(clause.Locking{Strength: "UPDATE"}).
				Where("shop_id = ? AND series_id = ? AND status = ?", allocatorShopID, req.SeriesID, constants.StatusEnabled).
				First(&parentSeriesAllocation).Error; lockErr != nil {
				return errors.New(errors.CodeForbidden, "当前账号无有效系列授权，无法向下分配")
			}
		}

		validatedPackages, validateErr := s.validateInitialGrantPackages(ctx, tx, req, allocatorShopID)
		if validateErr != nil {
			return validateErr
		}

		txSeriesStore := postgres.NewShopSeriesAllocationStore(tx)
		if createErr := txSeriesStore.Create(ctx, allocation); createErr != nil {
			return errors.Wrap(errors.CodeDatabaseError, createErr, "创建系列授权失败")
		}

		// 所有套餐已在当前事务内完成批量校验，此处只写入同一业务单元。
		txPkgStore := postgres.NewShopPackageAllocationStore(tx)
		txHistoryStore := postgres.NewShopPackageAllocationPriceHistoryStore(tx)
		for _, validated := range validatedPackages {
			item := validated.item
			pkg := validated.packageModel
			pkgAlloc := &model.ShopPackageAllocation{
				ShopID:                  req.ShopID,
				PackageID:               item.PackageID,
				AllocatorShopID:         allocatorShopID,
				CostPrice:               *item.CostPrice,
				RetailPrice:             pkg.SuggestedRetailPrice,
				RetailPriceConfigStatus: pkg.PriceConfigStatus,
				SeriesAllocationID:      &allocation.ID,
				ExpiryBaseOverride:      expiryBaseOverride,
				Status:                  constants.StatusEnabled,
				ShelfStatus:             constants.ShelfStatusOn,
			}
			pkgAlloc.Creator = operatorID
			pkgAlloc.Updater = operatorID
			if err := txPkgStore.Create(ctx, pkgAlloc); err != nil {
				return errors.Wrap(errors.CodeDatabaseError, err, "创建套餐分配失败")
			}
			if err := txHistoryStore.Create(ctx, &model.ShopPackageAllocationPriceHistory{
				AllocationID:  pkgAlloc.ID,
				OldCostPrice:  0,
				NewCostPrice:  *item.CostPrice,
				ChangeReason:  "初始授权",
				ChangedBy:     operatorID,
				EffectiveFrom: time.Now(),
			}); err != nil {
				return errors.Wrap(errors.CodeDatabaseError, err, "创建套餐价格历史失败")
			}
		}

		return nil
	})
	if err != nil {
		return nil, err
	}

	// 事务提交后构建完整响应（此时 packages 已可查询到）
	return s.buildGrantResponse(ctx, allocation, series, config)
}

// Get 查询单条系列授权详情
// GET /api/admin/shop-series-grants/:id
func (s *Service) Get(ctx context.Context, id uint) (*dto.ShopSeriesGrantResponse, error) {
	allocation, err := s.shopSeriesAllocationStore.GetByID(ctx, id)
	if err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, errors.New(errors.CodeNotFound, "授权记录不存在")
		}
		return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询授权记录失败")
	}

	series, err := s.packageSeriesStore.GetByID(ctx, allocation.SeriesID)
	if err != nil {
		return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询套餐系列失败")
	}
	config, err := series.GetOneTimeCommissionConfig()
	if err != nil || config == nil {
		config = &model.OneTimeCommissionConfig{}
	}

	return s.buildGrantResponse(ctx, allocation, series, config)
}

// List 分页查询系列授权列表
// GET /api/admin/shop-series-grants
func (s *Service) List(ctx context.Context, req *dto.ShopSeriesGrantListRequest) (*dto.ShopSeriesGrantPageResult, error) {
	page := req.Page
	if page <= 0 {
		page = 1
	}
	pageSize := req.PageSize
	if pageSize <= 0 {
		pageSize = constants.DefaultPageSize
	}
	if pageSize > constants.MaxPageSize {
		pageSize = constants.MaxPageSize
	}

	opts := &store.QueryOptions{
		Page:     page,
		PageSize: pageSize,
		OrderBy:  "id DESC",
	}
	filters := make(map[string]interface{})
	if req.ShopID != nil {
		filters["shop_id"] = *req.ShopID
	}
	if req.SeriesID != nil {
		filters["series_id"] = *req.SeriesID
	}
	if req.AllocatorShopID != nil {
		filters["allocator_shop_id"] = *req.AllocatorShopID
	}
	if req.Status != nil {
		filters["status"] = *req.Status
	}

	allocations, total, err := s.shopSeriesAllocationStore.List(ctx, opts, filters)
	if err != nil {
		return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询授权列表失败")
	}

	// 批量查询店铺名称
	shopIDs := make([]uint, 0)
	seriesIDs := make([]uint, 0)
	for _, a := range allocations {
		shopIDs = append(shopIDs, a.ShopID)
		if a.AllocatorShopID > 0 {
			shopIDs = append(shopIDs, a.AllocatorShopID)
		}
		seriesIDs = append(seriesIDs, a.SeriesID)
	}
	shopMap := make(map[uint]string)
	if len(shopIDs) > 0 {
		shops, _ := s.shopStore.GetByIDs(ctx, shopIDs)
		for _, sh := range shops {
			shopMap[sh.ID] = sh.ShopName
		}
	}
	seriesMap := make(map[uint]*model.PackageSeries)
	if len(seriesIDs) > 0 {
		seriesList, _ := s.packageSeriesStore.GetByIDs(ctx, seriesIDs)
		for _, sr := range seriesList {
			seriesMap[sr.ID] = sr
		}
	}

	items := make([]*dto.ShopSeriesGrantListItem, 0, len(allocations))
	for _, a := range allocations {
		item := &dto.ShopSeriesGrantListItem{
			ID:                      a.ID,
			ShopID:                  a.ShopID,
			ShopName:                shopMap[a.ShopID],
			SeriesID:                a.SeriesID,
			AllocatorShopID:         a.AllocatorShopID,
			OneTimeCommissionAmount: a.OneTimeCommissionAmount,
			// 强充有效状态在 seriesMap 分支中计算，此处先设默认值
			ForceRechargeEnabled: a.EnableForceRecharge,
			ForceRechargeAmount:  a.ForceRechargeAmount,
			Status:               a.Status,
			CreatedAt:            a.CreatedAt.Format(time.DateTime),
		}
		if a.AllocatorShopID > 0 {
			item.AllocatorShopName = shopMap[a.AllocatorShopID]
		} else {
			item.AllocatorShopName = "平台"
		}
		if sr, ok := seriesMap[a.SeriesID]; ok {
			item.SeriesName = sr.SeriesName
			config, _ := sr.GetOneTimeCommissionConfig()
			if config != nil {
				item.CommissionType = config.CommissionType
				// 计算强充有效状态：first_recharge 或平台已启用 accumulated_recharge 强充时锁定
				forceRechargeLocked := config.TriggerType == model.OneTimeCommissionTriggerFirstRecharge || config.EnableForceRecharge
				item.ForceRechargeLocked = forceRechargeLocked
				if forceRechargeLocked {
					item.ForceRechargeEnabled = true
					item.ForceRechargeAmount = config.ForceAmount
				} else {
					item.ForceRechargeEnabled = a.EnableForceRecharge
					item.ForceRechargeAmount = a.ForceRechargeAmount
				}
			}
		}
		// 统计套餐数量
		pkgCount, _ := s.shopPackageAllocationStore.CountBySeriesAllocationID(ctx, a.ID)
		item.PackageCount = int(pkgCount)
		items = append(items, item)
	}

	return &dto.ShopSeriesGrantPageResult{
		List:     items,
		Total:    total,
		Page:     page,
		PageSize: pageSize,
	}, nil
}

// Update 更新系列授权
// PUT /api/admin/shop-series-grants/:id
func (s *Service) Update(ctx context.Context, id uint, req *dto.UpdateShopSeriesGrantRequest) (*dto.ShopSeriesGrantResponse, error) {
	operatorID := middleware.GetUserIDFromContext(ctx)
	operatorShopID := middleware.GetShopIDFromContext(ctx)

	allocation, err := s.shopSeriesAllocationStore.GetByID(ctx, id)
	if err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, errors.New(errors.CodeNotFound, "授权记录不存在")
		}
		return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询授权记录失败")
	}

	// 代理只能修改自己分配出去的授权
	if operatorShopID > 0 && allocation.AllocatorShopID != operatorShopID {
		return nil, errors.New(errors.CodeForbidden, "无权限操作该授权记录")
	}

	series, err := s.packageSeriesStore.GetByID(ctx, allocation.SeriesID)
	if err != nil {
		return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询套餐系列失败")
	}
	config, err := series.GetOneTimeCommissionConfig()
	if err != nil || config == nil {
		return nil, errors.New(errors.CodeInternalError, "获取系列佣金配置失败")
	}

	// 更新固定模式佣金金额
	if req.OneTimeCommissionAmount != nil && config.CommissionType == "fixed" {
		ceiling, ceilErr := s.getParentCeilingFixed(ctx, allocation.AllocatorShopID, allocation.SeriesID, config)
		if ceilErr != nil {
			return nil, ceilErr
		}
		if *req.OneTimeCommissionAmount > ceiling {
			return nil, errors.New(errors.CodeInvalidParam, "佣金金额不能超过上级天花板")
		}
		allocation.OneTimeCommissionAmount = *req.OneTimeCommissionAmount
	}

	// 更新梯度模式阶梯
	if len(req.CommissionTiers) > 0 && config.CommissionType == "tiered" {
		if len(req.CommissionTiers) != len(config.Tiers) {
			return nil, errors.New(errors.CodeInvalidParam, "梯度阶梯数量与系列配置不一致")
		}
		ceilingMap, ceilErr := s.getParentCeilingTiered(ctx, allocation.AllocatorShopID, allocation.SeriesID, config.Tiers)
		if ceilErr != nil {
			return nil, ceilErr
		}
		agentTiers := make([]model.AllocationCommissionTier, 0, len(req.CommissionTiers))
		for i, tier := range req.CommissionTiers {
			if tier.Threshold != config.Tiers[i].Threshold {
				return nil, errors.New(errors.CodeInvalidParam, "梯度阶梯 threshold 与系列配置不匹配")
			}
			if ceiling, ok := ceilingMap[tier.Threshold]; ok && tier.Amount > ceiling {
				return nil, errors.New(errors.CodeInvalidParam, "某档位佣金金额超过上级天花板")
			}
			agentTiers = append(agentTiers, model.AllocationCommissionTier{
				Threshold: tier.Threshold,
				Amount:    tier.Amount,
			})
		}
		if err := allocation.SetCommissionTiers(agentTiers); err != nil {
			return nil, errors.Wrap(errors.CodeInternalError, err, "序列化阶梯佣金失败")
		}
	}

	// 更新强充配置（平台已锁定时忽略）
	forceRechargeLocked := config.TriggerType == model.OneTimeCommissionTriggerFirstRecharge || config.EnableForceRecharge
	if !forceRechargeLocked && req.EnableForceRecharge != nil {
		allocation.EnableForceRecharge = *req.EnableForceRecharge
		if req.ForceRechargeAmount != nil {
			allocation.ForceRechargeAmount = *req.ForceRechargeAmount
		}
	}

	allocation.Updater = operatorID
	if err := s.shopSeriesAllocationStore.Update(ctx, allocation); err != nil {
		return nil, errors.Wrap(errors.CodeDatabaseError, err, "更新授权记录失败")
	}

	return s.buildGrantResponse(ctx, allocation, series, config)
}

// ManagePackages 管理授权套餐（新增/更新/删除）
// PUT /api/admin/shop-series-grants/:id/packages
func (s *Service) ManagePackages(ctx context.Context, id uint, req *dto.ManageGrantPackagesRequest) (*dto.ShopSeriesGrantResponse, error) {
	expiryBaseOverride, err := packagepkg.ValidateExpiryBaseOverride(req.ExpiryBaseOverride, req.ExpiryBaseOverrideSet)
	if err != nil {
		return nil, err
	}
	operatorID := middleware.GetUserIDFromContext(ctx)
	operatorShopID := middleware.GetShopIDFromContext(ctx)

	allocation, err := s.shopSeriesAllocationStore.GetByID(ctx, id)
	if err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, errors.New(errors.CodeNotFound, "授权记录不存在")
		}
		return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询授权记录失败")
	}

	// 代理只能操作自己分配的授权
	if operatorShopID > 0 && allocation.AllocatorShopID != operatorShopID {
		return nil, errors.New(errors.CodeForbidden, "无权限操作该授权记录")
	}

	err = s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		txPkgStore := postgres.NewShopPackageAllocationStore(tx)
		txHistoryStore := postgres.NewShopPackageAllocationPriceHistoryStore(tx)

		for _, item := range req.Packages {
			if item.Remove != nil && *item.Remove {
				// 软删除已有的有效分配
				existing, findErr := txPkgStore.GetByShopAndPackageForSystem(ctx, allocation.ShopID, item.PackageID)
				if findErr != nil {
					// 已不存在时按幂等成功处理
					continue
				}
				if deleteErr := txPkgStore.Delete(ctx, existing.ID); deleteErr != nil {
					return errors.Wrap(errors.CodeDatabaseError, deleteErr, "删除套餐分配失败")
				}
				continue
			}

			if item.CostPrice == nil {
				return errors.New(errors.CodeInvalidParam, "新增或修改套餐授权时必须提交成本价")
			}
			costPrice := *item.CostPrice

			// 新增或更新套餐分配
			existing, findErr := txPkgStore.GetByShopAndPackageForSystem(ctx, allocation.ShopID, item.PackageID)
			if findErr == nil {
				// 已有记录：更新成本价并写历史
				oldPrice := existing.CostPrice
				if oldPrice != costPrice {
					// cost_price 锁定检查：存在下级分配记录时禁止修改
					var subCount int64
					if countErr := tx.Model(&model.ShopPackageAllocation{}).
						Where("allocator_shop_id = ? AND package_id = ? AND deleted_at IS NULL", allocation.ShopID, item.PackageID).
						Count(&subCount).Error; countErr != nil {
						return errors.Wrap(errors.CodeDatabaseError, countErr, "查询下级套餐分配失败")
					}
					if subCount > 0 {
						return errors.New(errors.CodeForbidden, "存在下级分配记录，请先回收后再修改成本价")
					}
				}
				existing.CostPrice = costPrice
				existing.Updater = operatorID
				if updateErr := txPkgStore.Update(ctx, existing); updateErr != nil {
					return errors.Wrap(errors.CodeDatabaseError, updateErr, "更新套餐分配失败")
				}
				if oldPrice != costPrice {
					if historyErr := txHistoryStore.Create(ctx, &model.ShopPackageAllocationPriceHistory{
						AllocationID:  existing.ID,
						OldCostPrice:  oldPrice,
						NewCostPrice:  costPrice,
						ChangeReason:  "手动调价",
						ChangedBy:     operatorID,
						EffectiveFrom: time.Now(),
					}); historyErr != nil {
						return errors.Wrap(errors.CodeDatabaseError, historyErr, "创建套餐价格历史失败")
					}
				}
			} else {
				pkg, pkgErr := s.packageStore.GetByID(ctx, item.PackageID)
				if pkgErr != nil || pkg.SeriesID != allocation.SeriesID {
					return errors.New(errors.CodeInvalidParam, "套餐不属于该系列，无法添加到此授权")
				}
				if pkg.IsGift {
					return errors.New(errors.CodeForbidden, "赠送套餐不能分配给代理")
				}
				if allocation.AllocatorShopID > 0 {
					_, authErr := s.shopPackageAllocationStore.GetByShopAndPackageForSystem(ctx, allocation.AllocatorShopID, item.PackageID)
					if authErr != nil {
						return errors.New(errors.CodeForbidden, "无权限分配该套餐")
					}
				}
				pkgAlloc := &model.ShopPackageAllocation{
					ShopID:                  allocation.ShopID,
					PackageID:               item.PackageID,
					AllocatorShopID:         allocation.AllocatorShopID,
					CostPrice:               costPrice,
					RetailPrice:             pkg.SuggestedRetailPrice,
					RetailPriceConfigStatus: pkg.PriceConfigStatus,
					SeriesAllocationID:      &allocation.ID,
					ExpiryBaseOverride:      expiryBaseOverride,
					Status:                  constants.StatusEnabled,
					ShelfStatus:             constants.ShelfStatusOn,
				}
				pkgAlloc.Creator = operatorID
				pkgAlloc.Updater = operatorID
				if createErr := txPkgStore.Create(ctx, pkgAlloc); createErr != nil {
					return errors.Wrap(errors.CodeDatabaseError, createErr, "创建套餐分配失败")
				}
				if historyErr := txHistoryStore.Create(ctx, &model.ShopPackageAllocationPriceHistory{
					AllocationID:  pkgAlloc.ID,
					OldCostPrice:  0,
					NewCostPrice:  costPrice,
					ChangeReason:  "新增授权",
					ChangedBy:     operatorID,
					EffectiveFrom: time.Now(),
				}); historyErr != nil {
					return errors.Wrap(errors.CodeDatabaseError, historyErr, "创建套餐价格历史失败")
				}
			}
		}
		return nil
	})
	if err != nil {
		return nil, err
	}

	// 重新查询最新状态
	return s.Get(ctx, id)
}

// Delete 删除系列授权（软删除）
// DELETE /api/admin/shop-series-grants/:id
func (s *Service) Delete(ctx context.Context, id uint) error {
	operatorShopID := middleware.GetShopIDFromContext(ctx)

	allocation, err := s.shopSeriesAllocationStore.GetByID(ctx, id)
	if err != nil {
		if err == gorm.ErrRecordNotFound {
			return errors.New(errors.CodeNotFound, "授权记录不存在")
		}
		return errors.Wrap(errors.CodeDatabaseError, err, "查询授权记录失败")
	}

	// 代理只能删除自己分配的授权
	if operatorShopID > 0 && allocation.AllocatorShopID != operatorShopID {
		return errors.New(errors.CodeForbidden, "无权限操作该授权记录")
	}

	// 检查是否有子级授权依赖（该店铺是否作为 allocator 向下分配了）
	subAllocations, err := s.shopSeriesAllocationStore.GetByAllocatorShopID(ctx, allocation.ShopID)
	for _, sub := range subAllocations {
		if sub.SeriesID == allocation.SeriesID {
			return errors.New(errors.CodeConflict, "该授权存在下级依赖，请先删除下级授权")
		}
	}
	_ = err

	// 事务软删除 ShopSeriesAllocation + 所有关联 ShopPackageAllocation
	return s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		txSeriesStore := postgres.NewShopSeriesAllocationStore(tx)
		txPkgStore := postgres.NewShopPackageAllocationStore(tx)

		pkgAllocations, _ := txPkgStore.GetBySeriesAllocationID(ctx, id)
		for _, pa := range pkgAllocations {
			if delErr := txPkgStore.Delete(ctx, pa.ID); delErr != nil {
				return errors.Wrap(errors.CodeDatabaseError, delErr, "删除套餐分配失败")
			}
		}

		if delErr := txSeriesStore.Delete(ctx, id); delErr != nil {
			return errors.Wrap(errors.CodeDatabaseError, delErr, "删除系列授权失败")
		}
		return nil
	})
}
