package user

import (
	"context"
	"fmt"
	"time"

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

	"github.com/golang-jwt/jwt/v5"
)

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, time.Time, error) {
	_, 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
	}
	return token, 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")
	}
	cnt, err := s.readDB.ShippingRecords.WithContext(ctx).Where(s.readDB.ShippingRecords.InventoryID.Eq(claims.InventoryID)).Count()
	if err == nil && cnt > 0 {
		return 0, fmt.Errorf("already_processed")
	}
	arow := &model.UserAddresses{UserID: claims.OwnerUserID, Name: name, Mobile: mobile, Province: province, City: city, District: district, Address: address, IsDefault: 0}
	if err := s.writeDB.UserAddresses.WithContext(ctx).Create(arow); err != nil {
		return 0, err
	}
	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")
	}
	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, remark) VALUES (?,?,?,?,?,?,?,?,?,?)", claims.OwnerUserID, inv.OrderID, 0, claims.InventoryID, inv.ProductID, 1, price, arow.ID, 1, "shared_address_submit"); 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", claims.InventoryID, claims.OwnerUserID); db.Error != nil {
		err = db.Error
		return 0, err
	}
	return arow.ID, 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)).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) (int64, []int64, []struct {
	ID     int64
	Reason string
}, []struct {
	ID     int64
	Reason string
}, error) {
	if len(inventoryIDs) == 0 {
		return 0, nil, nil, []struct {
			ID     int64
			Reason string
		}{{ID: 0, Reason: "invalid_params"}}, nil
	}
	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
	}
	var addrID int64
	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, err := s.readDB.UserAddresses.WithContext(ctx).Where(s.readDB.UserAddresses.UserID.Eq(userID), s.readDB.UserAddresses.IsDefault.Eq(1)).First()
		if err != nil || da == nil {
			return 0, nil, nil, []struct {
				ID     int64
				Reason string
			}{{ID: 0, Reason: "no_default_address"}}, nil
		}
		addrID = da.ID
	}

	// 生成批次号（只有多个有效项时才生成）
	batchNo := ""
	if len(uniq) > 1 {
		batchNo = generateBatchNo(userID)
	}

	success := make([]int64, 0, len(uniq))
	skipped := make([]struct {
		ID     int64
		Reason string
	}, 0)
	failed := make([]struct {
		ID     int64
		Reason string
	}, 0)
	for _, id := range uniq {
		inv, _ := s.readDB.UserInventory.WithContext(ctx).Where(s.readDB.UserInventory.ID.Eq(id)).First()
		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 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
		}
		if _, err := s.RequestShippingWithBatch(ctx, userID, id, batchNo, addrID); err != nil {
			failed = append(failed, struct {
				ID     int64
				Reason string
			}{ID: id, Reason: err.Error()})
			continue
		}
		success = append(success, id)
	}
	return addrID, 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")
	}
	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")
	}
	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
		}
	}
	type itemInfo struct {
		pid int64
		pts int64
	}
	infos := make(map[int64]itemInfo, len(uniq))
	for _, id := range uniq {
		inv, err := s.readDB.UserInventory.WithContext(ctx).Where(s.readDB.UserInventory.UserID.Eq(userID), s.readDB.UserInventory.ID.Eq(id), 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
		}
		points := p.Price * rate
		infos[id] = itemInfo{pid: inv.ProductID, pts: points}
	}
	var total int64
	for _, id := range uniq {
		info := infos[id]
		if err := s.AddPoints(ctx, userID, info.pts, "redeem_reward", fmt.Sprintf("inventory:%d product:%d", id, info.pid), 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", info.pts, id, userID); db.Error != nil {
			err := db.Error
			return 0, err
		}
		total += info.pts
	}
	return total, 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
}
