package shop

import (
	"context"
	"math"
	"strings"

	"gorm.io/gorm"

	"github.com/break/junhong_cmp_fiber/internal/model"
	"github.com/break/junhong_cmp_fiber/internal/model/dto"
	"github.com/break/junhong_cmp_fiber/pkg/constants"
	"github.com/break/junhong_cmp_fiber/pkg/errors"
	"github.com/break/junhong_cmp_fiber/pkg/middleware"
)

// FundSummaryQuery 提供数据权限范围内的店铺资金概况读取投影。
type FundSummaryQuery struct {
	db *gorm.DB
}

// NewFundSummaryQuery 创建店铺资金概况 Query。
func NewFundSummaryQuery(db *gorm.DB) *FundSummaryQuery {
	return &FundSummaryQuery{db: db}
}

// List 分页查询店铺，并以固定批次数投影主钱包、佣金、提现和主账号信息。
func (q *FundSummaryQuery) List(ctx context.Context, req dto.ShopFundSummaryListReq) (*dto.ShopFundSummaryPageResult, error) {
	page, pageSize := normalizeFundSummaryPage(req.Page, req.PageSize)
	base := middleware.ApplyShopIDFilter(ctx, q.db.WithContext(ctx).Model(&model.Shop{}))
	base = applyFundSummaryFilters(base, req)

	var total int64
	if err := base.Count(&total).Error; err != nil {
		return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询店铺资金概况总数失败")
	}
	var shops []model.Shop
	if err := base.Order("created_at DESC, id DESC").Offset((page - 1) * pageSize).Limit(pageSize).Find(&shops).Error; err != nil {
		return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询店铺资金概况列表失败")
	}
	if len(shops) == 0 {
		return &dto.ShopFundSummaryPageResult{Items: []dto.ShopFundSummaryItem{}, Total: total, Page: page, Size: pageSize}, nil
	}

	shopIDs := make([]uint, 0, len(shops))
	for index := range shops {
		shopIDs = append(shopIDs, shops[index].ID)
	}
	wallets, err := q.loadWallets(ctx, shopIDs)
	if err != nil {
		return nil, err
	}
	withdrawals, err := q.loadWithdrawals(ctx, shopIDs)
	if err != nil {
		return nil, err
	}
	accounts, err := q.loadPrimaryAccounts(ctx, shopIDs)
	if err != nil {
		return nil, err
	}

	items := make([]dto.ShopFundSummaryItem, 0, len(shops))
	for index := range shops {
		item, err := projectFundSummary(shops[index], wallets[shops[index].ID], withdrawals[shops[index].ID], accounts[shops[index].ID])
		if err != nil {
			return nil, err
		}
		items = append(items, item)
	}
	return &dto.ShopFundSummaryPageResult{Items: items, Total: total, Page: page, Size: pageSize}, nil
}

type fundWallets struct {
	main       *model.AgentWallet
	commission *model.AgentWallet
}

type withdrawalAmounts struct {
	approved int64
	pending  int64
}

type withdrawalAggregate struct {
	ShopID uint
	Status int
	Amount int64
}

func normalizeFundSummaryPage(page, pageSize int) (int, int) {
	if page <= 0 {
		page = constants.DefaultPage
	}
	if pageSize <= 0 {
		pageSize = constants.DefaultPageSize
	}
	if pageSize > constants.MaxPageSize {
		pageSize = constants.MaxPageSize
	}
	return page, pageSize
}

func applyFundSummaryFilters(db *gorm.DB, req dto.ShopFundSummaryListReq) *gorm.DB {
	if shopName := strings.TrimSpace(req.ShopName); shopName != "" {
		db = db.Where("shop_name ILIKE ?", "%"+shopName+"%")
	}
	if username := strings.TrimSpace(req.Username); username != "" {
		db = db.Where(`EXISTS (
			SELECT 1 FROM tb_account AS account_filter
			WHERE account_filter.shop_id = tb_shop.id
			  AND account_filter.is_primary = TRUE
			  AND account_filter.deleted_at IS NULL
			  AND account_filter.username ILIKE ?
		)`, "%"+username+"%")
	}
	return db
}

func (q *FundSummaryQuery) loadWallets(ctx context.Context, shopIDs []uint) (map[uint]fundWallets, error) {
	var records []model.AgentWallet
	if err := q.db.WithContext(ctx).
		Where("shop_id IN ? AND wallet_type IN ?", shopIDs, []string{constants.AgentWalletTypeMain, constants.AgentWalletTypeCommission}).
		Order("id ASC").Find(&records).Error; err != nil {
		return nil, errors.Wrap(errors.CodeDatabaseError, err, "批量查询店铺钱包失败")
	}
	result := make(map[uint]fundWallets, len(shopIDs))
	for index := range records {
		wallet := &records[index]
		pair := result[wallet.ShopID]
		switch wallet.WalletType {
		case constants.AgentWalletTypeMain:
			if pair.main == nil {
				pair.main = wallet
			}
		case constants.AgentWalletTypeCommission:
			if pair.commission == nil {
				pair.commission = wallet
			}
		}
		result[wallet.ShopID] = pair
	}
	return result, nil
}

func (q *FundSummaryQuery) loadWithdrawals(ctx context.Context, shopIDs []uint) (map[uint]withdrawalAmounts, error) {
	var records []withdrawalAggregate
	if err := q.db.WithContext(ctx).Model(&model.CommissionWithdrawalRequest{}).
		Select("shop_id, status, COALESCE(SUM(amount), 0) AS amount").
		Where("shop_id IN ? AND status IN ?", shopIDs, []int{constants.WithdrawalStatusApproved, constants.WithdrawalStatusPending}).
		Group("shop_id, status").Scan(&records).Error; err != nil {
		return nil, errors.Wrap(errors.CodeDatabaseError, err, "批量查询店铺提现汇总失败")
	}
	result := make(map[uint]withdrawalAmounts, len(shopIDs))
	for _, record := range records {
		amounts := result[record.ShopID]
		if record.Status == constants.WithdrawalStatusApproved {
			amounts.approved = record.Amount
		} else if record.Status == constants.WithdrawalStatusPending {
			amounts.pending = record.Amount
		}
		result[record.ShopID] = amounts
	}
	return result, nil
}

func (q *FundSummaryQuery) loadPrimaryAccounts(ctx context.Context, shopIDs []uint) (map[uint]*model.Account, error) {
	var records []model.Account
	if err := q.db.WithContext(ctx).
		Where("shop_id IN ? AND is_primary = ? AND deleted_at IS NULL", shopIDs, true).
		Order("id ASC").Find(&records).Error; err != nil {
		return nil, errors.Wrap(errors.CodeDatabaseError, err, "批量查询店铺主账号失败")
	}
	result := make(map[uint]*model.Account, len(shopIDs))
	for index := range records {
		account := &records[index]
		if account.ShopID != nil && result[*account.ShopID] == nil {
			result[*account.ShopID] = account
		}
	}
	return result, nil
}

func projectFundSummary(shop model.Shop, wallets fundWallets, withdrawals withdrawalAmounts, account *model.Account) (dto.ShopFundSummaryItem, error) {
	mainProjection, err := projectMainWallet(wallets.main)
	if err != nil {
		return dto.ShopFundSummaryItem{}, err
	}
	commissionProjection, err := projectCommissionWallet(wallets.commission, withdrawals)
	if err != nil {
		return dto.ShopFundSummaryItem{}, err
	}
	item := dto.ShopFundSummaryItem{
		ShopID: shop.ID, ShopName: shop.ShopName, ShopCode: shop.ShopCode,
		MainBalance: mainProjection.balance, MainFrozenBalance: mainProjection.frozen,
		CashAvailableBalance: mainProjection.cashAvailable, CreditEnabled: mainProjection.creditEnabled,
		CreditLimit: mainProjection.creditLimit, AvailableBalance: mainProjection.available,
		IsInDebt: mainProjection.isInDebt, DebtAmount: mainProjection.debtAmount, Version: mainProjection.version,
		TotalCommission: commissionProjection.total, WithdrawnCommission: withdrawals.approved,
		UnwithdrawCommission: commissionProjection.unwithdrawn, FrozenCommission: commissionProjection.frozen,
		WithdrawingCommission: withdrawals.pending, AvailableCommission: commissionProjection.available,
		CreatedAt: shop.CreatedAt.Format("2006-01-02 15:04:05"),
	}
	if account != nil {
		item.Username = account.Username
		item.Phone = account.Phone
	}
	return item, nil
}

type mainWalletProjection struct {
	balance       int64
	frozen        int64
	cashAvailable int64
	creditEnabled bool
	creditLimit   int64
	available     int64
	isInDebt      bool
	debtAmount    int64
	version       int
}

func projectMainWallet(wallet *model.AgentWallet) (mainWalletProjection, error) {
	if wallet == nil {
		return mainWalletProjection{}, nil
	}
	cashAvailable, ok := safeFundSub(wallet.Balance, wallet.FrozenBalance)
	if !ok {
		return mainWalletProjection{}, errors.New(errors.CodeInternalError, "计算主钱包现金可用金额时发生整数溢出")
	}
	effectiveCredit := int64(0)
	if wallet.CreditEnabled {
		effectiveCredit = wallet.CreditLimit
	}
	available, ok := safeFundAdd(cashAvailable, effectiveCredit)
	if !ok {
		return mainWalletProjection{}, errors.New(errors.CodeInternalError, "计算主钱包总可用金额时发生整数溢出")
	}
	debtAmount := int64(0)
	if wallet.Balance < 0 {
		if wallet.Balance == math.MinInt64 {
			return mainWalletProjection{}, errors.New(errors.CodeInternalError, "计算主钱包欠款金额时发生整数溢出")
		}
		debtAmount = -wallet.Balance
	}
	return mainWalletProjection{
		balance: wallet.Balance, frozen: wallet.FrozenBalance, cashAvailable: cashAvailable,
		creditEnabled: wallet.CreditEnabled, creditLimit: wallet.CreditLimit, available: available,
		isInDebt: wallet.Balance < 0, debtAmount: debtAmount, version: wallet.Version,
	}, nil
}

type commissionProjection struct {
	total       int64
	unwithdrawn int64
	frozen      int64
	available   int64
}

func projectCommissionWallet(wallet *model.AgentWallet, withdrawals withdrawalAmounts) (commissionProjection, error) {
	balance, frozen := int64(0), int64(0)
	if wallet != nil {
		balance = wallet.Balance
		frozen = wallet.FrozenBalance
	}
	unwithdrawn, ok := safeFundAdd(balance, frozen)
	if !ok {
		return commissionProjection{}, errors.New(errors.CodeInternalError, "计算未提现佣金时发生整数溢出")
	}
	total, ok := safeFundAdd(unwithdrawn, withdrawals.approved)
	if !ok {
		return commissionProjection{}, errors.New(errors.CodeInternalError, "计算累计佣金时发生整数溢出")
	}
	available, ok := safeFundSub(balance, withdrawals.pending)
	if !ok {
		return commissionProjection{}, errors.New(errors.CodeInternalError, "计算可提现佣金时发生整数溢出")
	}
	if available < 0 {
		available = 0
	}
	return commissionProjection{total: total, unwithdrawn: unwithdrawn, frozen: frozen, available: available}, nil
}

func safeFundAdd(left, right int64) (int64, bool) {
	if (right > 0 && left > math.MaxInt64-right) || (right < 0 && left < math.MinInt64-right) {
		return 0, false
	}
	return left + right, true
}

func safeFundSub(left, right int64) (int64, bool) {
	if (right > 0 && left < math.MinInt64+right) || (right < 0 && left > math.MaxInt64+right) {
		return 0, false
	}
	return left - right, true
}
