package shop_package_batch_allocation

import (
	"context"
	"strconv"

	"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/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"
	"gorm.io/gorm"
)

type Service struct {
	db                     *gorm.DB
	packageStore           *postgres.PackageStore
	packageAllocationStore *postgres.ShopPackageAllocationStore
	seriesAllocationStore  *postgres.ShopSeriesAllocationStore
	shopStore              *postgres.ShopStore
	auditService           AuditLogger
}

// AuditLogger 敏感配置变更审计能力。
type AuditLogger interface {
	LogOperation(ctx context.Context, log *model.AccountOperationLog)
}

func New(
	db *gorm.DB,
	packageStore *postgres.PackageStore,
	packageAllocationStore *postgres.ShopPackageAllocationStore,
	seriesAllocationStore *postgres.ShopSeriesAllocationStore,
	shopStore *postgres.ShopStore,
	auditService AuditLogger,
) *Service {
	return &Service{
		db:                     db,
		packageStore:           packageStore,
		packageAllocationStore: packageAllocationStore,
		seriesAllocationStore:  seriesAllocationStore,
		shopStore:              shopStore,
		auditService:           auditService,
	}
}

// UpdateExpiryBase 修改单条套餐分配的生效条件覆盖。
func (s *Service) UpdateExpiryBase(ctx context.Context, id uint, req *dto.UpdateAllocationExpiryBaseRequest) (*dto.ShopPackageAllocationTermsResponse, error) {
	override, err := packagepkg.ValidateExpiryBaseOverride(req.ExpiryBaseOverride, req.ExpiryBaseOverrideSet)
	if err != nil {
		return nil, err
	}
	allocation, err := s.packageAllocationStore.GetByID(ctx, id)
	if err != nil {
		return nil, errors.New(errors.CodeForbidden, "无权限操作该资源或资源不存在")
	}
	pkg, err := s.packageStore.GetByID(ctx, allocation.PackageID)
	if err != nil {
		return nil, errors.New(errors.CodeForbidden, "无权限操作该资源或资源不存在")
	}
	before := allocation.ExpiryBaseOverride
	if !sameNullableString(before, override) {
		allocation.ExpiryBaseOverride = override
		allocation.Updater = middleware.GetUserIDFromContext(ctx)
		if err := s.packageAllocationStore.Update(ctx, allocation); err != nil {
			return nil, errors.Wrap(errors.CodeDatabaseError, err, "更新套餐分配生效条件失败")
		}
		s.logExpiryBaseAudit(ctx, allocation, before, override)
	}
	return buildTermsResponse(pkg, allocation), nil
}

func buildTermsResponse(pkg *model.Package, allocation *model.ShopPackageAllocation) *dto.ShopPackageAllocationTermsResponse {
	effective := packagepkg.EffectiveExpiryBase(pkg, allocation)
	return &dto.ShopPackageAllocationTermsResponse{
		ID: allocation.ID, DefaultExpiryBase: pkg.ExpiryBase,
		DefaultExpiryBaseName:  packagepkg.ExpiryBaseName(pkg.ExpiryBase),
		ExpiryBaseOverride:     allocation.ExpiryBaseOverride,
		ExpiryBaseOverrideName: packagepkg.ExpiryBaseOverrideName(allocation.ExpiryBaseOverride),
		EffectiveExpiryBase:    effective, EffectiveExpiryBaseName: packagepkg.ExpiryBaseName(effective),
	}
}

func sameNullableString(left, right *string) bool {
	return left == nil && right == nil || left != nil && right != nil && *left == *right
}

func (s *Service) logExpiryBaseAudit(ctx context.Context, allocation *model.ShopPackageAllocation, before, after *string) {
	if s.auditService == nil {
		return
	}
	s.auditService.LogOperation(ctx, &model.AccountOperationLog{
		OperatorID: middleware.GetUserIDFromContext(ctx), OperatorType: middleware.GetUserTypeFromContext(ctx),
		OperatorName: middleware.GetUsernameFromContext(ctx), OperationType: "update_package_expiry_base",
		OperationDesc: "修改套餐分配生效条件覆盖: " + strconv.FormatUint(uint64(allocation.ID), 10),
		BeforeData:    model.JSONB{"allocation_id": allocation.ID, "expiry_base_override": before},
		AfterData:     model.JSONB{"allocation_id": allocation.ID, "expiry_base_override": after},
		RequestID:     middleware.GetRequestIDFromContext(ctx), IPAddress: middleware.GetIPFromContext(ctx), UserAgent: middleware.GetUserAgentFromContext(ctx),
	})
}

func (s *Service) BatchAllocate(ctx context.Context, req *dto.BatchAllocatePackagesRequest) (*dto.BatchAllocatePackagesResponse, error) {
	expiryBaseOverride, err := packagepkg.ValidateExpiryBaseOverride(req.ExpiryBaseOverride, req.ExpiryBaseOverrideSet)
	if err != nil {
		return nil, err
	}
	currentUserID := middleware.GetUserIDFromContext(ctx)
	if currentUserID == 0 {
		return nil, errors.New(errors.CodeUnauthorized, "未授权访问")
	}

	userType := middleware.GetUserTypeFromContext(ctx)
	allocatorShopID := middleware.GetShopIDFromContext(ctx)

	if userType == constants.UserTypeAgent && allocatorShopID == 0 {
		return nil, errors.New(errors.CodeUnauthorized, "当前用户不属于任何店铺")
	}

	targetShop, err := s.shopStore.GetByID(ctx, req.ShopID)
	if err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, errors.New(errors.CodeNotFound, "目标店铺不存在")
		}
		return nil, errors.Wrap(errors.CodeInternalError, err, "获取目标店铺失败")
	}

	if userType == constants.UserTypeAgent {
		if targetShop.ParentID == nil || *targetShop.ParentID != allocatorShopID {
			return nil, errors.New(errors.CodeForbidden, "只能分配给直属下级店铺")
		}
	}

	packages, err := s.getEnabledPackagesBySeries(ctx, req.SeriesID)
	if err != nil {
		return nil, err
	}

	if len(packages) == 0 {
		return nil, errors.New(errors.CodeInvalidParam, "该系列下没有启用的套餐")
	}

	// 检查目标店铺是否有该系列的分配
	seriesAllocation, err := s.seriesAllocationStore.GetByShopAndSeries(ctx, req.ShopID, req.SeriesID)
	if err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, errors.New(errors.CodeInvalidParam, "目标店铺没有该系列的分配权限")
		}
		return nil, errors.Wrap(errors.CodeInternalError, err, "查询系列分配失败")
	}

	result := &dto.BatchAllocatePackagesResponse{TotalPackages: len(packages), Allocations: []dto.ShopPackageAllocationTermsResponse{}}
	err = s.db.Transaction(func(tx *gorm.DB) error {
		txPkgAllocStore := postgres.NewShopPackageAllocationStore(tx)

		for _, pkg := range packages {
			// 已存在该套餐的分配记录则跳过，避免唯一约束冲突
			_, existErr := txPkgAllocStore.GetByShopAndPackageForSystem(ctx, req.ShopID, pkg.ID)
			if existErr == nil {
				result.SkippedCount++
				continue
			}

			costPrice := pkg.CostPrice
			if req.PriceAdjustment != nil {
				costPrice = s.calculateAdjustedPrice(pkg.CostPrice, req.PriceAdjustment)
			}

			allocation := &model.ShopPackageAllocation{
				BaseModel:               model.BaseModel{Creator: currentUserID, Updater: currentUserID},
				ShopID:                  req.ShopID,
				PackageID:               pkg.ID,
				AllocatorShopID:         allocatorShopID,
				CostPrice:               costPrice,
				RetailPrice:             pkg.SuggestedRetailPrice,
				RetailPriceConfigStatus: pkg.PriceConfigStatus,
				SeriesAllocationID:      &seriesAllocation.ID,
				ExpiryBaseOverride:      expiryBaseOverride,
				Status:                  constants.StatusEnabled,
			}
			if err := tx.Create(allocation).Error; err != nil {
				return errors.Wrap(errors.CodeInternalError, err, "创建套餐分配失败")
			}
			result.Allocations = append(result.Allocations, *buildTermsResponse(pkg, allocation))
		}

		return nil
	})
	if err != nil {
		return nil, err
	}
	result.AllocatedCount = len(result.Allocations)
	return result, nil
}

func (s *Service) getEnabledPackagesBySeries(ctx context.Context, seriesID uint) ([]*model.Package, error) {
	filters := map[string]interface{}{
		"series_id":    seriesID,
		"status":       constants.StatusEnabled,
		"shelf_status": 1,
	}

	packages, _, err := s.packageStore.List(ctx, nil, filters)
	if err != nil {
		return nil, errors.Wrap(errors.CodeInternalError, err, "获取套餐列表失败")
	}

	filtered := make([]*model.Package, 0, len(packages))
	for _, pkg := range packages {
		if pkg == nil || pkg.IsGift {
			continue
		}
		filtered = append(filtered, pkg)
	}

	return filtered, nil
}

func (s *Service) calculateAdjustedPrice(basePrice int64, adjustment *dto.PriceAdjustment) int64 {
	if adjustment == nil {
		return basePrice
	}

	if adjustment.Type == "fixed" {
		return basePrice + adjustment.Value
	}

	return basePrice + (basePrice * adjustment.Value / 1000)
}
