package user

import (
	"context"
	"fmt"
	"time"

	"bindbox-game/configs"
	"bindbox-game/internal/repository/mysql/model"

	"bindbox-game/internal/pkg/wechat"

	"github.com/golang-jwt/jwt/v5"
	"go.uber.org/zap"
	"gorm.io/gorm"
)

type shareClaims struct {
	OwnerUserID int64 `json:"owner_user_id"`
	InventoryID int64 `json:"inventory_id"`
	jwt.RegisteredClaims
}

func signShareToken(ownerUserID int64, inventoryID int64, expiresAt time.Time) (string, error) {
	claims := shareClaims{
		OwnerUserID: ownerUserID,
		InventoryID: inventoryID,
		RegisteredClaims: jwt.RegisteredClaims{
			NotBefore: jwt.NewNumericDate(time.Now()),
			IssuedAt:  jwt.NewNumericDate(time.Now()),
			ExpiresAt: jwt.NewNumericDate(expiresAt),
		},
	}
	return jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString([]byte(configs.Get().Random.CommitMasterKey))
}

func parseShareToken(tokenString string) (*shareClaims, error) {
	tokenClaims, err := jwt.ParseWithClaims(tokenString, &shareClaims{}, func(token *jwt.Token) (interface{}, error) {
		return []byte(configs.Get().Random.CommitMasterKey), nil
	})
	if tokenClaims != nil {
		if claims, ok := tokenClaims.Claims.(*shareClaims); ok && tokenClaims.Valid {
			return claims, nil
		}
	}
	return nil, err
}

func (s *service) CreateAddressShare(ctx context.Context, userID int64, inventoryID int64, expiresAt time.Time) (string, string, time.Time, error) {
	inv, err := s.readDB.UserInventory.WithContext(ctx).Where(s.readDB.UserInventory.UserID.Eq(userID), s.readDB.UserInventory.ID.Eq(inventoryID), s.readDB.UserInventory.Status.Eq(1)).First()
	if err != nil {
		return "", "", time.Time{}, err
	}
	token, err := signShareToken(userID, inventoryID, expiresAt)
	if err != nil {
		return "", "", time.Time{}, err
	}

	// 尝试生成微信小程序 ShortLink
	shortLink := ""
	c := configs.Get()
	if c.Wechat.AppID != "" && c.Wechat.AppSecret != "" {
		wcfg := &wechat.WechatConfig{AppID: c.Wechat.AppID, AppSecret: c.Wechat.AppSecret}
		at, errat := wechat.GetAccessTokenWithContext(ctx, wcfg)
		if errat == nil {
			pagePath := fmt.Sprintf("pages/address/submit?token=%s", token)
			pageTitle := "送你一个好礼，快来填写地址领走吧！"
			if inv.Remark != "" {
				pageTitle = fmt.Sprintf("送你一个%s，快来领走吧！", inv.Remark)
			}
			sl, errsl := wechat.GetShortLink(at, pagePath, pageTitle)
			if errsl == nil {
				shortLink = sl
				s.logger.Info("成功生成微信短链", zap.String("short_link", shortLink))
			} else {
				// 降级尝试生成 Scheme
				s.logger.Warn("生成微信短链失败，尝试降级为Scheme", zap.Error(errsl), zap.String("page_path", pagePath))
				// 修正 pagePath 格式，URL Scheme 需要 path 和 query 分离
				// 假设 pagePath 格式为 "pages/address/submit?token=xxx"
				schemePath := "pages/address/submit"
				schemeQuery := fmt.Sprintf("token=%s", token)

				scheme, errScheme := wechat.GenerateScheme(at, schemePath, schemeQuery, "release")
				if errScheme == nil {
					shortLink = scheme
					s.logger.Info("成功生成微信Scheme", zap.String("scheme", scheme))
				} else {
					s.logger.Error("生成微信Scheme也失败", zap.Error(errScheme))
				}
			}
		} else {
			s.logger.Error("获取微信AccessToken失败", zap.Error(errat))
		}
	} else {
		s.logger.Warn("微信配置缺失，跳过短链生成", zap.String("appid", c.Wechat.AppID))
	}

	return token, shortLink, expiresAt, nil
}

func (s *service) RevokeAddressShare(ctx context.Context, userID int64, inventoryID int64) error {
	return nil
}

func (s *service) SubmitAddressShare(ctx context.Context, shareToken string, name string, mobile string, province string, city string, district string, address string, submittedByUserID *int64, submittedIP *string) (int64, error) {
	claims, err := parseShareToken(shareToken)
	if err != nil {
		return 0, fmt.Errorf("invalid_or_expired_token")
	}

	// 1. 基本安全校验
	cnt, err := s.readDB.ShippingRecords.WithContext(ctx).Where(
		s.readDB.ShippingRecords.InventoryID.Eq(claims.InventoryID),
		s.readDB.ShippingRecords.Status.Neq(5), // 排除已取消
	).Count()
	if err == nil && cnt > 0 {
		return 0, fmt.Errorf("already_processed")
	}

	inv, err := s.readDB.UserInventory.WithContext(ctx).Where(s.readDB.UserInventory.ID.Eq(claims.InventoryID)).First()
	if err != nil {
		return 0, err
	}
	if inv.Status != 1 {
		return 0, fmt.Errorf("inventory_unavailable")
	}

	// 2. 确定资产最终归属地 (实名转赠逻辑)
	targetUserID := claims.OwnerUserID
	isTransfer := false
	if submittedByUserID != nil && *submittedByUserID > 0 && *submittedByUserID != claims.OwnerUserID {
		targetUserID = *submittedByUserID
		isTransfer = true
	}

	var addrID int64
	err = s.repo.GetDbW().Transaction(func(tx *gorm.DB) error {
		// a. 创建收货地址 (归属于 targetUserID)
		arow := &model.UserAddresses{
			UserID:    targetUserID,
			Name:      name,
			Mobile:    mobile,
			Province:  province,
			City:      city,
			District:  district,
			Address:   address,
			IsDefault: 0,
		}

		// Check if user has a default address
		cnt, _ := s.readDB.UserAddresses.WithContext(ctx).Where(s.readDB.UserAddresses.UserID.Eq(targetUserID), s.readDB.UserAddresses.IsDefault.Eq(1)).Count()
		if cnt == 0 {
			arow.IsDefault = 1
		}
		if err := tx.Omit("DefaultUserUnique").Create(arow).Error; err != nil {
			return err
		}
		addrID = arow.ID

		// b. 资产状态更新及所有权转移
		if isTransfer {
			// 记录转赠流水
			transferLog := &model.UserInventoryTransfers{
				InventoryID: claims.InventoryID,
				FromUserID:  claims.OwnerUserID,
				ToUserID:    targetUserID,
				Remark:      "address_share_transfer",
			}
			if err := tx.Create(transferLog).Error; err != nil {
				return err
			}

			// 更新资产所属人
			if err := tx.Table("user_inventory").Where("id = ? AND user_id = ? AND status = 1", claims.InventoryID, claims.OwnerUserID).
				Updates(map[string]interface{}{
					"user_id":    targetUserID,
					"status":     3,
					"updated_at": time.Now(),
					"remark":     fmt.Sprintf("transferred_from_%d|shipping_requested", claims.OwnerUserID),
				}).Error; err != nil {
				return err
			}
		} else {
			// 仅更新状态 (原主发货)
			if err := tx.Table("user_inventory").Where("id = ? AND user_id = ? AND status = 1", claims.InventoryID, claims.OwnerUserID).
				Updates(map[string]interface{}{
					"status":     3,
					"updated_at": time.Now(),
					"remark":     "shipping_requested_via_share",
				}).Error; err != nil {
				return err
			}
		}

		// c. 创建发货记录 (归属于 targetUserID)
		var price int64
		if inv.ProductID > 0 {
			var p model.Products
			if err := tx.Table("products").Where("id = ?", inv.ProductID).First(&p).Error; err == nil {
				price = p.Price
			}
		}

		// 生成转赠发货的批次号 (使用 T 前缀区分普通批发货的 B 前缀)
		transferBatchNo := fmt.Sprintf("T%d%d", targetUserID, time.Now().UnixNano()/1000000)

		shipRecord := &model.ShippingRecords{
			UserID:      targetUserID,
			OrderID:     inv.OrderID,
			OrderItemID: 0,
			InventoryID: claims.InventoryID,
			ProductID:   inv.ProductID,
			Quantity:    1,
			Price:       price,
			AddressID:   addrID,
			Status:      1,
			BatchNo:     transferBatchNo,
			Remark:      fmt.Sprintf("shared_address_submit|ip=%s|transfer_from=%d", *submittedIP, claims.OwnerUserID),
		}
		if err := tx.Omit("ShippedAt", "ReceivedAt").Create(shipRecord).Error; err != nil {
			return err
		}

		return nil
	})

	if err != nil {
		return 0, err
	}

	return addrID, nil
}

func (s *service) RequestShipping(ctx context.Context, userID int64, inventoryID int64) (int64, error) {
	return s.RequestShippingWithBatch(ctx, userID, inventoryID, "", 0)
}

// RequestShippingWithBatch 申请发货（支持批次号和指定地址）
func (s *service) RequestShippingWithBatch(ctx context.Context, userID int64, inventoryID int64, batchNo string, addrID int64) (int64, error) {
	cnt, err := s.readDB.ShippingRecords.WithContext(ctx).Where(
		s.readDB.ShippingRecords.InventoryID.Eq(inventoryID),
		s.readDB.ShippingRecords.Status.Neq(5), // Ignore cancelled
	).Count()
	if err == nil && cnt > 0 {
		return 0, fmt.Errorf("already_processed")
	}
	inv, err := s.readDB.UserInventory.WithContext(ctx).Where(s.readDB.UserInventory.UserID.Eq(userID), s.readDB.UserInventory.ID.Eq(inventoryID), s.readDB.UserInventory.Status.Eq(1)).First()
	if err != nil {
		return 0, err
	}

	// 如果没有传入地址ID，使用默认地址
	if addrID <= 0 {
		addr, err := s.readDB.UserAddresses.WithContext(ctx).Where(s.readDB.UserAddresses.UserID.Eq(userID), s.readDB.UserAddresses.IsDefault.Eq(1)).First()
		if err != nil {
			return 0, err
		}
		addrID = addr.ID
	}

	var price int64
	if inv.ProductID > 0 {
		if p, e := s.readDB.Products.WithContext(ctx).Where(s.readDB.Products.ID.Eq(inv.ProductID)).First(); e == nil && p != nil {
			price = p.Price
		}
	}

	if db := s.repo.GetDbW().Exec("INSERT INTO shipping_records (user_id, order_id, order_item_id, inventory_id, product_id, quantity, price, address_id, status, batch_no, remark) VALUES (?,?,?,?,?,?,?,?,?,?,?)", userID, inv.OrderID, 0, inventoryID, inv.ProductID, 1, price, addrID, 1, batchNo, "user_request_shipping"); db.Error != nil {
		err = db.Error
		return 0, err
	}
	if db := s.repo.GetDbW().Exec("UPDATE user_inventory SET status=3, updated_at=NOW(3), remark=CONCAT(IFNULL(remark,''),'|shipping_requested') WHERE id=? AND user_id=? AND status=1", inventoryID, userID); db.Error != nil {
		err = db.Error
		return 0, err
	}
	return addrID, nil
}

// generateBatchNo 生成唯一批次号
func generateBatchNo(userID int64) string {
	return fmt.Sprintf("B%d%d", userID, time.Now().UnixNano()/1000000)
}

func (s *service) RequestShippings(ctx context.Context, userID int64, inventoryIDs []int64, addressID *int64) (addrID int64, batchNo string, success []int64, skipped []struct {
	ID     int64
	Reason string
}, failed []struct {
	ID     int64
	Reason string
}, err error) {
	if len(inventoryIDs) == 0 {
		return 0, "", nil, nil, []struct {
			ID     int64
			Reason string
		}{{ID: 0, Reason: "invalid_params"}}, nil
	}

	// 1. 去重
	dedup := make(map[int64]struct{}, len(inventoryIDs))
	uniq := make([]int64, 0, len(inventoryIDs))
	for _, id := range inventoryIDs {
		if id > 0 {
			if _, ok := dedup[id]; !ok {
				dedup[id] = struct{}{}
				uniq = append(uniq, id)
			}
		}
	}
	if len(uniq) == 0 {
		return 0, "", nil, nil, []struct {
			ID     int64
			Reason string
		}{{ID: 0, Reason: "invalid_params"}}, nil
	}

	// 2. 获取收货地址
	if addressID != nil && *addressID > 0 {
		ua, _ := s.readDB.UserAddresses.WithContext(ctx).Where(s.readDB.UserAddresses.ID.Eq(*addressID), s.readDB.UserAddresses.UserID.Eq(userID)).First()
		if ua == nil {
			return 0, "", nil, nil, []struct {
				ID     int64
				Reason string
			}{{ID: 0, Reason: "address_not_found"}}, nil
		}
		addrID = ua.ID
	} else {
		da, e := s.readDB.UserAddresses.WithContext(ctx).Where(s.readDB.UserAddresses.UserID.Eq(userID), s.readDB.UserAddresses.IsDefault.Eq(1)).First()
		if e != nil || da == nil {
			return 0, "", nil, nil, []struct {
				ID     int64
				Reason string
			}{{ID: 0, Reason: "no_default_address"}}, nil
		}
		addrID = da.ID
	}

	// 3. 生成批次号
	batchNo = generateBatchNo(userID)

	// 4. 批量查询所有inventory（一次查询替代N次）
	invList, _ := s.readDB.UserInventory.WithContext(ctx).
		Where(s.readDB.UserInventory.ID.In(uniq...)).
		Find()

	// 构建inventory映射
	invMap := make(map[int64]*model.UserInventory, len(invList))
	for _, inv := range invList {
		invMap[inv.ID] = inv
	}

	// 5. 批量查询已有发货记录（检查哪些已处理）
	var existingShipInvIDs []int64
	_ = s.repo.GetDbR().Raw("SELECT DISTINCT inventory_id FROM shipping_records WHERE inventory_id IN ? AND status != 5", uniq).Scan(&existingShipInvIDs).Error
	existingShipMap := make(map[int64]struct{}, len(existingShipInvIDs))
	for _, id := range existingShipInvIDs {
		existingShipMap[id] = struct{}{}
	}

	// 6. 分类处理
	success = make([]int64, 0, len(uniq))
	skipped = make([]struct {
		ID     int64
		Reason string
	}, 0)
	failed = make([]struct {
		ID     int64
		Reason string
	}, 0)

	validInvs := make([]*model.UserInventory, 0, len(uniq))
	productIDs := make([]int64, 0, len(uniq))
	productIDSet := make(map[int64]struct{})

	for _, id := range uniq {
		inv := invMap[id]
		if inv == nil {
			skipped = append(skipped, struct {
				ID     int64
				Reason string
			}{ID: id, Reason: "not_found"})
			continue
		}
		if inv.UserID != userID {
			skipped = append(skipped, struct {
				ID     int64
				Reason string
			}{ID: id, Reason: "not_owned"})
			continue
		}
		if _, exists := existingShipMap[id]; exists {
			skipped = append(skipped, struct {
				ID     int64
				Reason string
			}{ID: id, Reason: "already_processed"})
			continue
		}
		if inv.Status == 3 {
			skipped = append(skipped, struct {
				ID     int64
				Reason string
			}{ID: id, Reason: "already_requested"})
			continue
		}
		if inv.Status != 1 {
			skipped = append(skipped, struct {
				ID     int64
				Reason string
			}{ID: id, Reason: "invalid_status"})
			continue
		}
		validInvs = append(validInvs, inv)
		if _, ok := productIDSet[inv.ProductID]; !ok && inv.ProductID > 0 {
			productIDSet[inv.ProductID] = struct{}{}
			productIDs = append(productIDs, inv.ProductID)
		}
	}

	if len(validInvs) == 0 {
		return addrID, batchNo, success, skipped, failed, nil
	}

	// 7. 批量查询products获取价格（一次查询替代N次）
	productMap := make(map[int64]int64) // productID -> price
	if len(productIDs) > 0 {
		prods, _ := s.readDB.Products.WithContext(ctx).Where(s.readDB.Products.ID.In(productIDs...)).Find()
		for _, p := range prods {
			productMap[p.ID] = p.Price
		}
	}

	// 8. 单事务批量处理
	validIDs := make([]int64, 0, len(validInvs))
	err = s.repo.GetDbW().Transaction(func(tx *gorm.DB) error {
		// 批量插入shipping_records
		for _, inv := range validInvs {
			price := productMap[inv.ProductID]
			if errExec := tx.Exec(
				"INSERT INTO shipping_records (user_id, order_id, order_item_id, inventory_id, product_id, quantity, price, address_id, status, batch_no, remark) VALUES (?,?,?,?,?,?,?,?,?,?,?)",
				userID, inv.OrderID, 0, inv.ID, inv.ProductID, 1, price, addrID, 1, batchNo, "batch_request_shipping",
			).Error; errExec != nil {
				return errExec
			}
			validIDs = append(validIDs, inv.ID)
		}

		// 批量更新inventory状态（一次UPDATE替代N次）
		if len(validIDs) > 0 {
			if errExec := tx.Exec(
				"UPDATE user_inventory SET status=3, updated_at=NOW(3), remark=CONCAT(IFNULL(remark,''),'|batch_shipping_requested') WHERE id IN ? AND user_id=? AND status=1",
				validIDs, userID,
			).Error; errExec != nil {
				return errExec
			}
		}

		return nil
	})

	if err != nil {
		// 事务失败，所有都标记为failed
		for _, inv := range validInvs {
			failed = append(failed, struct {
				ID     int64
				Reason string
			}{ID: inv.ID, Reason: err.Error()})
		}
		return addrID, batchNo, success, skipped, failed, nil
	}

	success = validIDs
	return addrID, batchNo, success, skipped, failed, nil
}

func (s *service) RedeemInventoryToPoints(ctx context.Context, userID int64, inventoryID int64) (int64, error) {
	inv, err := s.readDB.UserInventory.WithContext(ctx).Where(s.readDB.UserInventory.UserID.Eq(userID), s.readDB.UserInventory.ID.Eq(inventoryID), s.readDB.UserInventory.Status.Eq(1)).First()
	if err != nil {
		return 0, err
	}
	p, err := s.readDB.Products.WithContext(ctx).Where(s.readDB.Products.ID.Eq(inv.ProductID)).First()
	if err != nil {
		return 0, err
	}
	cfg, _ := s.readDB.SystemConfigs.WithContext(ctx).Where(s.readDB.SystemConfigs.ConfigKey.Eq("points_exchange_per_cent")).First()
	rate := int64(1)
	if cfg != nil {
		var r int64
		_, _ = fmt.Sscanf(cfg.ConfigValue, "%d", &r)
		if r > 0 {
			rate = r
		}
	}
	points := p.Price * rate
	if err = s.AddPoints(ctx, userID, points, "redeem_reward", fmt.Sprintf("inventory:%d product:%d", inventoryID, inv.ProductID), nil, nil); err != nil {
		return 0, err
	}
	if db := s.repo.GetDbW().Exec("UPDATE user_inventory SET status=3, remark=CONCAT(IFNULL(remark,''),'|redeemed_points=',?) WHERE id=? AND user_id=? AND status=1", points, inventoryID, userID); db.Error != nil {
		err = db.Error
		return 0, err
	}
	return points, nil
}

func (s *service) RedeemInventoriesToPoints(ctx context.Context, userID int64, inventoryIDs []int64) (int64, error) {
	if len(inventoryIDs) == 0 {
		return 0, fmt.Errorf("invalid_params")
	}

	// 1. 去重
	dedup := make(map[int64]struct{})
	uniq := make([]int64, 0, len(inventoryIDs))
	for _, id := range inventoryIDs {
		if id <= 0 {
			continue
		}
		if _, ok := dedup[id]; !ok {
			dedup[id] = struct{}{}
			uniq = append(uniq, id)
		}
	}
	if len(uniq) == 0 {
		return 0, fmt.Errorf("invalid_params")
	}

	// 2. 获取兑换比率（只查询一次）
	cfg, _ := s.readDB.SystemConfigs.WithContext(ctx).Where(s.readDB.SystemConfigs.ConfigKey.Eq("points_exchange_per_cent")).First()
	rate := int64(1)
	if cfg != nil {
		var r int64
		_, _ = fmt.Sscanf(cfg.ConfigValue, "%d", &r)
		if r > 0 {
			rate = r
		}
	}

	// 3. 批量查询所有inventory（一次查询替代N次）
	invList, err := s.readDB.UserInventory.WithContext(ctx).
		Where(s.readDB.UserInventory.ID.In(uniq...)).
		Where(s.readDB.UserInventory.UserID.Eq(userID)).
		Where(s.readDB.UserInventory.Status.Eq(1)).
		Find()
	if err != nil {
		return 0, err
	}
	if len(invList) == 0 {
		return 0, fmt.Errorf("no_valid_inventory")
	}

	// 构建inventory映射和收集productID
	invMap := make(map[int64]*model.UserInventory, len(invList))
	productIDs := make([]int64, 0, len(invList))
	productIDSet := make(map[int64]struct{})
	for _, inv := range invList {
		invMap[inv.ID] = inv
		if _, ok := productIDSet[inv.ProductID]; !ok {
			productIDSet[inv.ProductID] = struct{}{}
			productIDs = append(productIDs, inv.ProductID)
		}
	}

	// 4. 批量查询所有products（一次查询替代N次）
	products, err := s.readDB.Products.WithContext(ctx).
		Where(s.readDB.Products.ID.In(productIDs...)).
		Find()
	if err != nil {
		return 0, err
	}
	productMap := make(map[int64]*model.Products, len(products))
	for _, p := range products {
		productMap[p.ID] = p
	}

	// 5. 计算总积分和准备批量更新数据
	var totalPoints int64
	validIDs := make([]int64, 0, len(invList))
	for _, inv := range invList {
		p := productMap[inv.ProductID]
		if p == nil {
			continue
		}
		points := p.Price * rate
		totalPoints += points
		validIDs = append(validIDs, inv.ID)
	}

	if len(validIDs) == 0 {
		return 0, fmt.Errorf("no_valid_products")
	}

	// 6. 单事务处理：添加积分 + 批量更新inventory状态
	err = s.repo.GetDbW().Transaction(func(tx *gorm.DB) error {
		// 添加积分（一次性添加总积分）
		now := time.Now()
		ledger := &model.UserPointsLedger{
			UserID:   userID,
			Action:   "redeem_reward",
			Points:   totalPoints,
			RefTable: "user_inventory",
			RefID:    fmt.Sprintf("batch:%d", len(validIDs)),
			Remark:   fmt.Sprintf("batch_redeem_%d_items", len(validIDs)),
		}
		if err := tx.Create(ledger).Error; err != nil {
			return err
		}

		// 更新积分余额
		pointRecord := &model.UserPoints{
			UserID:     userID,
			Kind:       "redeem_reward",
			Points:     totalPoints,
			ValidStart: now,
			ValidEnd:   now.AddDate(100, 0, 0), // 100年有效期
		}
		if err := tx.Create(pointRecord).Error; err != nil {
			return err
		}

		// 批量更新inventory状态（一次UPDATE替代N次）
		if err := tx.Exec(
			"UPDATE user_inventory SET status=3, updated_at=NOW(3), remark=CONCAT(IFNULL(remark,''),'|batch_redeemed') WHERE id IN ? AND user_id=? AND status=1",
			validIDs, userID,
		).Error; err != nil {
			return err
		}

		return nil
	})

	if err != nil {
		return 0, err
	}

	return totalPoints, nil
}

func (s *service) VoidUserInventory(ctx context.Context, adminID int64, userID int64, inventoryID int64) error {
	if userID <= 0 || inventoryID <= 0 {
		return fmt.Errorf("invalid_params")
	}
	inv, err := s.readDB.UserInventory.WithContext(ctx).
		Where(s.readDB.UserInventory.ID.Eq(inventoryID)).
		Where(s.readDB.UserInventory.UserID.Eq(userID)).
		First()
	if err != nil {
		return err
	}
	if inv.Status != 1 {
		return fmt.Errorf("invalid_status")
	}
	if db := s.repo.GetDbW().Exec("UPDATE user_inventory SET status=2, updated_at=NOW(3), remark=CONCAT(IFNULL(remark,''),'|void_by_admin') WHERE id=? AND user_id=? AND status=1", inventoryID, userID); db.Error != nil {
		return db.Error
	}
	_ = adminID
	return nil
}
