package order

import (
	"context"
	"encoding/json"
	"time"

	"hfb_sys/backend/internal/model"
	"hfb_sys/backend/internal/modules/chat"
	"hfb_sys/backend/internal/modules/notification"

	"gorm.io/datatypes"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"
)

func (r *Repository) Create(ctx context.Context, renterID uint64, req CreateRequest) (*OrderDTO, error) {
	var createdID uint64
	err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		var listing model.RentalListing
		if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&listing, req.ListingID).Error; err != nil {
			return err
		}
		if listing.Status != listingStatusPublished || listing.ReviewStatus != listingReviewStatusApproved || listing.InTransaction {
			return ErrListingUnavailable
		}
		if listing.OwnerID == renterID {
			return ErrCannotRentOwnListing
		}

		var account model.GameAccount
		if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&account, listing.AccountID).Error; err != nil {
			return err
		}
		snapshot, err := makeAccountSnapshot(account, listing)
		if err != nil {
			return err
		}
		orderNo, err := newOrderNo()
		if err != nil {
			return err
		}
		rentHours := internalOrderHours
		pricing := buildOrderPricing(listing, account)
		depositOriginalAmountCent := listing.DepositAmountCent
		paidDepositAmountCent, waivedDepositAmountCent, err := r.depositAmountsForOrder(tx, renterID, depositOriginalAmountCent)
		if err != nil {
			return err
		}
		order := model.RentalOrder{
			OrderNo:                   orderNo,
			ListingID:                 listing.ID,
			AccountID:                 listing.AccountID,
			OwnerID:                   listing.OwnerID,
			RenterID:                  renterID,
			EstimatedDurationHours:    rentHours,
			RentAmountCent:            pricing.RentAmountCent,
			OwnerRentAmountCent:       pricing.OwnerRentAmountCent,
			DepositAmountCent:         paidDepositAmountCent,
			DepositOriginalAmountCent: depositOriginalAmountCent,
			DepositWaivedAmountCent:   waivedDepositAmountCent,
			PlatformFeeCent:           pricing.PlatformFeeCent,
			AccountSnapshot:           snapshot,
			Status:                    orderStatusPendingPayment,
			HandoffStatus:             handoffStatusNone,
			SettlementStatus:          settlementStatusUnsettled,
		}
		if err := tx.Create(&order).Error; err != nil {
			return err
		}
		orderID := order.ID
		if err := notification.Append(tx,
			notification.Entry{
				UserID:  order.RenterID,
				Type:    "order",
				Title:   "订单已创建",
				Content: "订单已创建，请在有效时间内完成支付。",
				BizType: "order",
				BizID:   &orderID,
			},
		); err != nil {
			return err
		}
		reserveListingForOrder(&listing)
		if err := tx.Save(&listing).Error; err != nil {
			return err
		}
		createdID = order.ID
		return nil
	})
	if err != nil {
		return nil, err
	}
	return r.FindForUser(ctx, renterID, createdID)
}

func (r *Repository) depositAmountsForOrder(tx *gorm.DB, renterID uint64, originalDepositCent int64) (int64, int64, error) {
	if originalDepositCent <= 0 {
		return 0, 0, nil
	}
	var user model.User
	if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&user, renterID).Error; err != nil {
		return 0, 0, err
	}
	used, err := activeDepositFreeUsed(tx, renterID)
	if err != nil {
		return 0, 0, err
	}
	paidDeposit, waivedDeposit := calculateDepositWaiver(originalDepositCent, user.DepositFreeQuotaCent, used)
	return paidDeposit, waivedDeposit, nil
}

func activeDepositFreeUsed(tx *gorm.DB, renterID uint64) (int64, error) {
	var used int64
	err := tx.Model(&model.RentalOrder{}).
		Where("renter_id = ? AND status NOT IN ?",
			renterID,
			[]string{orderStatusCompleted, orderStatusCancelled, orderStatusClosed},
		).
		Select("COALESCE(SUM(deposit_waived_amount_cent), 0)").
		Scan(&used).Error
	return used, err
}

func calculateDepositWaiver(originalDepositCent int64, quotaCent int64, usedCent int64) (int64, int64) {
	remaining := maxCent(quotaCent-usedCent, 0)
	waived := minCent(originalDepositCent, remaining)
	paid := maxCent(originalDepositCent-waived, 0)
	return paid, waived
}

// Pay 保留旧接口兼容，但真实付款必须走 payment 模块的渠道支付入口。
func (r *Repository) Pay(ctx context.Context, userID uint64, orderID uint64) error {
	return ErrChannelPaymentRequired
}

// ConfirmPaidFromChannel 在乐刷确认支付后推进订单状态；租客资金不进入站内钱包。
func (r *Repository) ConfirmPaidFromChannel(ctx context.Context, orderID uint64, providerBizNo string) error {
	var newConvID uint64
	err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		convID, err := r.ConfirmPaidFromChannelTx(tx, orderID)
		if err != nil {
			return err
		}
		newConvID = convID
		return nil
	})
	if err != nil {
		return err
	}
	if newConvID > 0 && r.chatNotifier != nil {
		r.chatNotifier.NotifyNewConversation(newConvID)
	}
	return nil
}

// ConfirmPaidFromChannelTx 在外部事务内推进支付成功后的订单状态。
// 调用方负责提交事务，并在提交成功后按返回的会话 ID 触发异步通知。
func (r *Repository) ConfirmPaidFromChannelTx(tx *gorm.DB, orderID uint64) (uint64, error) {
	assets, err := r.lockOrderAssets(tx, orderID)
	if err != nil {
		return 0, err
	}
	order := assets.Order
	listing := assets.Listing
	account := assets.Account
	if order.Status == orderStatusPendingHandoff || order.Status == orderStatusRenting {
		return 0, nil
	}
	if order.Status != orderStatusPendingPayment {
		return 0, ErrOrderCannotPay
	}

	if listing.Status != listingStatusPublished || listing.ReviewStatus != listingReviewStatusApproved || !listing.InTransaction {
		return 0, ErrListingUnavailable
	}

	// 租客已通过外部渠道付款，这里不写租客钱包流水。
	orderID = order.ID
	now := time.Now()
	order.Status = orderStatusPendingHandoff
	order.HandoffStatus = handoffStatusPendingOwner
	order.HandoffStartedAt = &now
	markAssetsRented(listing, account)

	// 拉租客进发布群(替代原来的建订单群)
	conversationID := uint64(0)
	if err := chat.AddRenterToListingConversation(tx, listing.ID, order.RenterID, order.OrderNo); err != nil {
		return 0, err
	}
	// 查询发布群ID用于返回
	var listingConv model.ChatConversation
	if err := tx.Where("listing_id = ?", listing.ID).First(&listingConv).Error; err == nil {
		conversationID = listingConv.ID
	}

	if err := notification.Append(tx,
		notification.Entry{
			UserID:  order.OwnerID,
			Type:    "order",
			Title:   "收到新的租号订单",
			Content: "租客已完成支付，请尽快提交交接说明。",
			BizType: "order",
			BizID:   &orderID,
		},
		notification.Entry{
			UserID:  order.RenterID,
			Type:    "order",
			Title:   "订单支付成功",
			Content: "支付已完成，等待号主提交交接说明。",
			BizType: "order",
			BizID:   &orderID,
		},
	); err != nil {
		return 0, err
	}
	if err := tx.Save(order).Error; err != nil {
		return 0, err
	}
	if err := tx.Save(listing).Error; err != nil {
		return 0, err
	}
	if err := tx.Save(account).Error; err != nil {
		return 0, err
	}
	return conversationID, nil
}

func (r *Repository) Cancel(ctx context.Context, userID uint64, orderID uint64) error {
	var refund *refundAction
	err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		var order model.RentalOrder
		if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
			Where("id = ? AND renter_id = ?", orderID, userID).
			First(&order).Error; err != nil {
			return err
		}
		if order.Status != orderStatusPendingPayment && order.Status != orderStatusPendingHandoff {
			return ErrOrderCannotCancel
		}
		listing, account, err := r.lockListingAccountForOrder(tx, &order)
		if err != nil {
			return err
		}

		beforeStatus := order.Status
		order.Status = orderStatusCancelled
		order.HandoffStatus = handoffStatusCancelled
		orderID := order.ID
		if beforeStatus == orderStatusPendingHandoff {
			totalCent := order.RentAmountCent + order.DepositAmountCent
			action, err := r.prepareRefund(&order, totalCent, refundBizCancel, "取消订单原路退款")
			if err != nil {
				return err
			}
			refund = action
		}
		if err := notification.Append(tx,
			notification.Entry{
				UserID:  order.OwnerID,
				Type:    "order",
				Title:   "订单已取消",
				Content: "租客已取消订单，账号已重新释放。",
				BizType: "order",
				BizID:   &orderID,
			},
			notification.Entry{
				UserID:  order.RenterID,
				Type:    "order",
				Title:   "订单取消成功",
				Content: "订单已取消，退款将原路退回您的支付账户。",
				BizType: "order",
				BizID:   &orderID,
			},
		); err != nil {
			return err
		}
		releaseAssetsForRental(listing, account)
		if err := closePendingOrderPayments(tx, order.ID, "order_cancel"); err != nil {
			return err
		}

		// 订单取消,移出租客(仅付款后取消需要移出)
		if beforeStatus == orderStatusPendingHandoff {
			if err := chat.RemoveRenterFromListingConversation(tx, listing.ID, order.RenterID); err != nil {
				// 移出失败不阻塞订单取消
			}
		}

		if err := tx.Save(&order).Error; err != nil {
			return err
		}
		if err := tx.Save(listing).Error; err != nil {
			return err
		}
		return tx.Save(account).Error
	})
	if err != nil {
		return err
	}
	r.startRefundBestEffort(ctx, refund)
	return nil
}

// closePendingOrderPayments 在订单取消时关闭仍未完成的下单支付流水，避免后台一直显示“支付中”。
func closePendingOrderPayments(tx *gorm.DB, orderID uint64, source string) error {
	raw, err := json.Marshal(map[string]string{
		"source": source,
		"reason": "订单已取消，关闭未完成支付单",
	})
	if err != nil {
		return err
	}
	return tx.Model(&model.PaymentOrder{}).
		Where("order_id = ? AND biz_type = ? AND status IN ?", orderID, "order_pay", []string{"created", "paying"}).
		Updates(map[string]any{
			"status":       "closed",
			"raw_response": datatypes.JSON(raw),
		}).Error
}

func (r *Repository) ConfirmReceive(ctx context.Context, userID uint64, orderID uint64) error {
	return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		var order model.RentalOrder
		if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&order, orderID).Error; err != nil {
			return err
		}
		if order.RenterID != userID {
			return ErrPermissionDenied
		}
		if order.Status != orderStatusPendingHandoff || order.HandoffStatus != handoffStatusPendingRenterConfirm {
			return ErrOrderCannotReceive
		}
		now := time.Now()
		if err := tx.Model(&model.HandoffRecord{}).
			Where("order_id = ? AND type = ?", order.ID, "owner_handoff").
			Update("confirmed_by_renter_at", now).Error; err != nil {
			return err
		}
		order.Status = orderStatusRenting
		order.HandoffStatus = handoffStatusReceived
		durationHours := orderDurationHours(order)
		order.EstimatedDurationHours = durationHours
		order.RentedAt = &now
		orderID := order.ID
		if err := notification.Append(tx, notification.Entry{
			UserID:  order.OwnerID,
			Type:    "handoff",
			Title:   "租客已确认收号",
			Content: "订单已进入使用中。",
			BizType: "order",
			BizID:   &orderID,
		}); err != nil {
			return err
		}
		return tx.Save(&order).Error
	})
}
