package services

import (
	"context"
	"errors"
	"strings"
	"time"

	"quyun/v2/app/errorx"
	transaction_dto "quyun/v2/app/http/v1/dto"
	user_dto "quyun/v2/app/http/v1/dto"
	"quyun/v2/database/fields"
	"quyun/v2/database/models"
	"quyun/v2/pkg/consts"

	"github.com/google/uuid"
	"go.ipao.vip/gen/field"
	"go.ipao.vip/gen/types"
	"gorm.io/gorm"
)

// @provider
type order struct{}

func (s *order) ListUserOrders(ctx context.Context, tenantID, userID int64, status string) ([]user_dto.Order, error) {
	if userID == 0 {
		return nil, errorx.ErrUnauthorized
	}
	uid := userID

	tbl, q := models.OrderQuery.QueryContext(ctx)
	if tenantID > 0 {
		q = q.Where(field.Or(
			field.And(tbl.UserID.Eq(uid), tbl.TenantID.Eq(tenantID)),
			field.And(tbl.UserID.Eq(uid), tbl.Type.Eq(consts.OrderTypeRecharge)),
		))
	} else {
		q = q.Where(tbl.UserID.Eq(uid))
	}

	if status != "" && status != "all" {
		q = q.Where(tbl.Status.Eq(consts.OrderStatus(status)))
	}

	list, err := q.Order(tbl.CreatedAt.Desc()).Find()
	if err != nil {
		return nil, errorx.ErrDatabaseError.WithCause(err)
	}

	data, err := s.composeOrderListDTO(ctx, list)
	if err != nil {
		return nil, err
	}
	return data, nil
}

func (s *order) GetUserOrder(ctx context.Context, tenantID, userID, id int64) (*user_dto.Order, error) {
	if userID == 0 {
		return nil, errorx.ErrUnauthorized
	}
	uid := userID

	tbl, q := models.OrderQuery.QueryContext(ctx)
	itemQuery := q
	if tenantID > 0 {
		itemQuery = itemQuery.Where(field.Or(
			field.And(tbl.ID.Eq(id), tbl.UserID.Eq(uid), tbl.TenantID.Eq(tenantID)),
			field.And(tbl.ID.Eq(id), tbl.UserID.Eq(uid), tbl.Type.Eq(consts.OrderTypeRecharge)),
		))
	} else {
		itemQuery = itemQuery.Where(tbl.ID.Eq(id), tbl.UserID.Eq(uid))
	}
	item, err := itemQuery.First()
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, errorx.ErrRecordNotFound
		}
		return nil, errorx.ErrDatabaseError.WithCause(err)
	}

	dto, err := s.composeOrderDTO(ctx, item)
	if err != nil {
		return nil, err
	}
	return &dto, nil
}

func (s *order) Create(
	ctx context.Context,
	tenantID int64,
	userID int64,
	form *transaction_dto.OrderCreateForm,
) (*transaction_dto.OrderCreateResponse, error) {
	if userID == 0 {
		return nil, errorx.ErrUnauthorized
	}
	uid := userID
	cid := form.ContentID

	// 幂等控制：相同幂等键直接返回已创建的订单。
	idempotencyKey := ""
	if form.IdempotencyKey != nil {
		idempotencyKey = strings.TrimSpace(*form.IdempotencyKey)
	}
	if idempotencyKey != "" {
		tbl, q := models.OrderQuery.QueryContext(ctx)
		q = q.Where(tbl.UserID.Eq(uid), tbl.IdempotencyKey.Eq(idempotencyKey))
		if tenantID > 0 {
			q = q.Where(tbl.TenantID.Eq(tenantID))
		}
		existing, err := q.First()
		if err == nil {
			return &transaction_dto.OrderCreateResponse{OrderID: existing.ID}, nil
		}
		if !errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, errorx.ErrDatabaseError.WithCause(err)
		}
	}

	// 1. Fetch Content & Price
	contentQuery := models.ContentQuery.WithContext(ctx).Where(models.ContentQuery.ID.Eq(cid))
	if tenantID > 0 {
		contentQuery = contentQuery.Where(models.ContentQuery.TenantID.Eq(tenantID))
	}
	content, err := contentQuery.First()
	if err != nil {
		return nil, errorx.ErrRecordNotFound.WithMsg("内容不存在")
	}
	if content.Status != consts.ContentStatusPublished {
		return nil, errorx.ErrBusinessLogic.WithMsg("内容未发布")
	}

	price, err := models.ContentPriceQuery.WithContext(ctx).Where(models.ContentPriceQuery.ContentID.Eq(cid)).First()
	if err != nil {
		return nil, errorx.ErrDataCorrupted.WithCause(err).WithMsg("价格信息缺失")
	}

	amountOriginal := price.PriceAmount
	var amountDiscount int64 = 0
	var couponID int64 = 0

	// Validate Coupon
	if form.UserCouponID > 0 {
		discount, err := Coupon.Validate(ctx, tenantID, uid, form.UserCouponID, amountOriginal)
		if err != nil {
			return nil, err
		}
		amountDiscount = discount

		uc, err := models.UserCouponQuery.WithContext(ctx).Where(models.UserCouponQuery.ID.Eq(form.UserCouponID)).First()
		if err == nil {
			couponID = uc.CouponID
		}
	}

	amountPaid := amountOriginal - amountDiscount

	// 2. Create Order (Status: Created)
	order := &models.Order{
		TenantID:       content.TenantID,
		UserID:         uid,
		Type:           consts.OrderTypeContentPurchase,
		Status:         consts.OrderStatusCreated,
		Currency:       consts.Currency(price.Currency),
		AmountOriginal: amountOriginal,
		AmountDiscount: amountDiscount,
		AmountPaid:     amountPaid,
		CouponID:       couponID,
		IdempotencyKey: idempotencyKey,
		Snapshot:       types.NewJSONType(fields.OrdersSnapshot{}),
	}
	if order.IdempotencyKey == "" {
		order.IdempotencyKey = uuid.NewString()
	}

	err = models.Q.Transaction(func(tx *models.Query) error {
		if err := tx.Order.WithContext(ctx).Create(order); err != nil {
			return err
		}

		// 3. Create Order Item
		item := &models.OrderItem{
			TenantID:      content.TenantID,
			UserID:        uid,
			OrderID:       order.ID,
			ContentID:     cid,
			ContentUserID: content.UserID,
			AmountPaid:    amountPaid,
		}
		if err := tx.OrderItem.WithContext(ctx).Create(item); err != nil {
			return err
		}

		// Mark Coupon Used
		if form.UserCouponID > 0 {
			if err := Coupon.MarkUsed(ctx, tx, tenantID, form.UserCouponID, order.ID); err != nil {
				return err
			}
		}

		return nil
	})
	if err != nil {
		if _, ok := err.(*errorx.AppError); ok {
			return nil, err
		}
		return nil, errorx.ErrDatabaseError.WithCause(err)
	}

	return &transaction_dto.OrderCreateResponse{
		OrderID: order.ID,
	}, nil
}

func (s *order) Pay(
	ctx context.Context,
	tenantID int64,
	userID int64,
	id int64,
	form *transaction_dto.OrderPayForm,
) (*transaction_dto.OrderPayResponse, error) {
	if userID == 0 {
		return nil, errorx.ErrUnauthorized
	}
	uid := userID

	// Fetch Order
	o, err := models.OrderQuery.WithContext(ctx).
		Where(models.OrderQuery.ID.Eq(id), models.OrderQuery.UserID.Eq(uid)).
		First()
	if err != nil {
		return nil, errorx.ErrRecordNotFound
	}
	if tenantID > 0 && o.TenantID > 0 && o.TenantID != tenantID {
		return nil, errorx.ErrForbidden.WithMsg("租户不匹配")
	}
	if o.Status != consts.OrderStatusCreated {
		return nil, errorx.ErrStatusConflict.WithMsg("订单状态不可支付")
	}

	if form.Method == "balance" {
		return s.payWithBalance(ctx, o)
	}

	// External payment (mock) - normally returns URL/params
	return &transaction_dto.OrderPayResponse{
		PayParams: "mock_pay_params",
	}, nil
}

// ProcessExternalPayment handles callback from payment gateway
func (s *order) ProcessExternalPayment(ctx context.Context, tenantID, orderID int64, externalID string) error {
	o, err := models.OrderQuery.WithContext(ctx).Where(models.OrderQuery.ID.Eq(orderID)).First()
	if err != nil {
		return errorx.ErrRecordNotFound
	}
	if tenantID > 0 && o.TenantID > 0 && o.TenantID != tenantID {
		return errorx.ErrForbidden.WithMsg("租户不匹配")
	}
	if o.Status != consts.OrderStatusCreated {
		return nil // Already processed idempotency
	}

	return s.settleOrder(ctx, o, "external", externalID)
}

func (s *order) payWithBalance(ctx context.Context, o *models.Order) (*transaction_dto.OrderPayResponse, error) {
	err := s.settleOrder(ctx, o, "balance", "")
	if err != nil {
		if _, ok := err.(*errorx.AppError); ok {
			return nil, err
		}
		return nil, errorx.ErrDatabaseError.WithCause(err)
	}

	return &transaction_dto.OrderPayResponse{
		PayParams: "balance_paid",
	}, nil
}

func (s *order) settleOrder(ctx context.Context, o *models.Order, method, externalID string) error {
	var tenantOwnerID int64
	// 关键结算事务：遇到数据库冲突/死锁时短暂退避重试，避免支付状态卡死。
	err := retryCriticalWrite(ctx, func() error {
		tenantOwnerID = 0
		return models.Q.Transaction(func(tx *models.Query) error {
			// 1. Handle Balance Updates
			if o.Type == consts.OrderTypeRecharge {
				// Income: Recharge (Credit User Balance)
				_, err := tx.User.WithContext(ctx).
					Where(tx.User.ID.Eq(o.UserID)).
					Update(tx.User.Balance, gorm.Expr("balance + ?", o.AmountPaid))
				if err != nil {
					return err
				}
			} else if method == "balance" {
				// Expense: Purchase with Balance (Deduct User Balance)
				info, err := tx.User.WithContext(ctx).
					Where(tx.User.ID.Eq(o.UserID), tx.User.Balance.Gte(o.AmountPaid)).
					Update(tx.User.Balance, gorm.Expr("balance - ?", o.AmountPaid))
				if err != nil {
					return err
				}
				if info.RowsAffected == 0 {
					return errorx.ErrQuotaExceeded.WithMsg("余额不足")
				}
			}

			// 2. Update Order Status
			now := time.Now()
			// snapshot := o.Snapshot // Preserve existing snapshot or update it with external ID
			// TODO: Update snapshot with payment info
			_, err := tx.Order.WithContext(ctx).Where(tx.Order.ID.Eq(o.ID)).Updates(&models.Order{
				Status:    consts.OrderStatusPaid,
				PaidAt:    now,
				UpdatedAt: now,
			})
			if err != nil {
				return err
			}

			// 3. Grant Content Access
			items, _ := tx.OrderItem.WithContext(ctx).Where(tx.OrderItem.OrderID.Eq(o.ID)).Find()
			for _, item := range items {
				// Check if access already exists (idempotency)
				exists, _ := tx.ContentAccess.WithContext(ctx).
					Where(tx.ContentAccess.UserID.Eq(o.UserID), tx.ContentAccess.ContentID.Eq(item.ContentID)).
					Exists()
				if exists {
					continue
				}

				access := &models.ContentAccess{
					TenantID:  item.TenantID,
					UserID:    o.UserID,
					ContentID: item.ContentID,
					OrderID:   o.ID,
					Status:    consts.ContentAccessStatusActive,
				}
				if err := tx.ContentAccess.WithContext(ctx).Save(access); err != nil {
					return err
				}
			}

			// 4. Create Tenant Ledger (Revenue) - Only for Content Purchase
			if o.Type == consts.OrderTypeContentPurchase {
				t, err := tx.Tenant.WithContext(ctx).Where(tx.Tenant.ID.Eq(o.TenantID)).First()
				if err != nil {
					return err
				}
				tenantOwnerID = t.UserID

				// Calculate Commission
				amount := o.AmountPaid
				fee := int64(float64(amount) * 0.10)
				creatorIncome := amount - fee

				// Credit Tenant Owner Balance (Net Income)
				_, err = tx.User.WithContext(ctx).
					Where(tx.User.ID.Eq(tenantOwnerID)).
					Update(tx.User.Balance, gorm.Expr("balance + ?", creatorIncome))
				if err != nil {
					return err
				}

				ledger := &models.TenantLedger{
					TenantID:       o.TenantID,
					UserID:         t.UserID, // Owner
					OrderID:        o.ID,
					Type:           consts.TenantLedgerTypeDebitPurchase, // Income from purchase
					Amount:         creatorIncome,
					BalanceBefore:  0, // TODO
					BalanceAfter:   0, // TODO
					FrozenBefore:   0,
					FrozenAfter:    0,
					IdempotencyKey: uuid.NewString(),
					Remark:         "内容销售收入 (扣除平台费)",
					OperatorUserID: o.UserID,
				}
				if err := tx.TenantLedger.WithContext(ctx).Create(ledger); err != nil {
					return err
				}
			}

			return nil
		})
	})
	if err != nil {
		return err
	}

	if Notification != nil {
		_ = Notification.Send(ctx, o.TenantID, o.UserID, "order", "支付成功", "订单已支付，您可以查看已购内容。")
		if tenantOwnerID > 0 {
			_ = Notification.Send(ctx, o.TenantID, tenantOwnerID, "order", "新的订单", "您的店铺有新的订单，收入已入账。")
		}
	}
	return nil
}

func (s *order) Status(ctx context.Context, tenantID, userID, id int64) (*transaction_dto.OrderStatusResponse, error) {
	o, err := models.OrderQuery.WithContext(ctx).Where(models.OrderQuery.ID.Eq(id)).First()
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, errorx.ErrRecordNotFound
		}
		return nil, errorx.ErrDatabaseError.WithCause(err)
	}
	if userID > 0 && o.UserID != userID {
		return nil, errorx.ErrForbidden.WithMsg("无权访问该订单")
	}
	if tenantID > 0 && o.TenantID > 0 && o.TenantID != tenantID {
		return nil, errorx.ErrForbidden.WithMsg("租户不匹配")
	}

	return &transaction_dto.OrderStatusResponse{
		Status: string(o.Status),
	}, nil
}

func (s *order) composeOrderDTO(ctx context.Context, o *models.Order) (user_dto.Order, error) {
	dto := user_dto.Order{
		ID:                o.ID,
		Type:              string(o.Type),
		TypeDescription:   o.Type.Description(),
		Status:            string(o.Status),
		StatusDescription: o.Status.Description(),
		Amount:            float64(o.AmountPaid) / 100.0,
		CreateTime:        o.CreatedAt.Format(time.RFC3339),
		TenantID:          o.TenantID,
	}

	// Fetch Tenant Name
	t, err := models.TenantQuery.WithContext(ctx).Where(models.TenantQuery.ID.Eq(o.TenantID)).First()
	if err == nil {
		dto.TenantName = t.Name
	}

	// Fetch Items
	items, err := models.OrderItemQuery.WithContext(ctx).Where(models.OrderItemQuery.OrderID.Eq(o.ID)).Find()
	if err == nil {
		dto.Quantity = len(items)
		for _, item := range items {
			// Fetch Content
			var c models.Content
			err := models.ContentQuery.WithContext(ctx).
				Where(models.ContentQuery.ID.Eq(item.ContentID)).
				UnderlyingDB().
				Preload("ContentAssets").
				Preload("ContentAssets.Asset").
				First(&c).Error

			if err == nil {
				ci := transaction_dto.ContentItem{
					ID:       c.ID,
					Title:    c.Title,
					Genre:    c.Genre,
					AuthorID: c.UserID,
					Price:    float64(item.AmountPaid) / 100.0, // Use actual paid amount
				}
				// Cover logic (simplified from content service)
				for _, asset := range c.ContentAssets {
					if asset.Role == consts.ContentAssetRoleCover && asset.Asset != nil {
						ci.Cover = Common.GetAssetURL(asset.Asset.ObjectKey)
						break
					}
				}
				dto.Items = append(dto.Items, ci)
			}
		}
	}

	return dto, nil
}

func (s *order) composeOrderListDTO(ctx context.Context, orders []*models.Order) ([]user_dto.Order, error) {
	if len(orders) == 0 {
		return []user_dto.Order{}, nil
	}

	// 批量收集订单、租户、内容 ID，避免逐条查询。
	orderIDs := make([]int64, 0, len(orders))
	tenantIDSet := make(map[int64]struct{}, len(orders))
	for _, o := range orders {
		orderIDs = append(orderIDs, o.ID)
		if o.TenantID > 0 {
			tenantIDSet[o.TenantID] = struct{}{}
		}
	}

	tenantIDs := make([]int64, 0, len(tenantIDSet))
	for id := range tenantIDSet {
		tenantIDs = append(tenantIDs, id)
	}

	tenantMap := make(map[int64]*models.Tenant, len(tenantIDs))
	if len(tenantIDs) > 0 {
		tbl, q := models.TenantQuery.QueryContext(ctx)
		list, err := q.Where(tbl.ID.In(tenantIDs...)).Find()
		if err != nil {
			return nil, errorx.ErrDatabaseError.WithCause(err)
		}
		for _, t := range list {
			tenantMap[t.ID] = t
		}
	}

	itemsByOrder := make(map[int64][]*models.OrderItem, len(orderIDs))
	contentIDSet := make(map[int64]struct{}, len(orderIDs))
	if len(orderIDs) > 0 {
		tbl, q := models.OrderItemQuery.QueryContext(ctx)
		items, err := q.Where(tbl.OrderID.In(orderIDs...)).Find()
		if err != nil {
			return nil, errorx.ErrDatabaseError.WithCause(err)
		}
		for _, item := range items {
			itemsByOrder[item.OrderID] = append(itemsByOrder[item.OrderID], item)
			if item.ContentID > 0 {
				contentIDSet[item.ContentID] = struct{}{}
			}
		}
	}

	contentIDs := make([]int64, 0, len(contentIDSet))
	for id := range contentIDSet {
		contentIDs = append(contentIDs, id)
	}

	contentMap := make(map[int64]*models.Content, len(contentIDs))
	if len(contentIDs) > 0 {
		var contents []*models.Content
		tbl, q := models.ContentQuery.QueryContext(ctx)
		err := q.Where(tbl.ID.In(contentIDs...)).
			UnderlyingDB().
			Preload("ContentAssets").
			Preload("ContentAssets.Asset").
			Find(&contents).Error
		if err != nil {
			return nil, errorx.ErrDatabaseError.WithCause(err)
		}
		for _, c := range contents {
			contentMap[c.ID] = c
		}
	}

	data := make([]user_dto.Order, 0, len(orders))
	for _, o := range orders {
		dto := user_dto.Order{
			ID:                o.ID,
			Type:              string(o.Type),
			TypeDescription:   o.Type.Description(),
			Status:            string(o.Status),
			StatusDescription: o.Status.Description(),
			Amount:            float64(o.AmountPaid) / 100.0,
			CreateTime:        o.CreatedAt.Format(time.RFC3339),
			TenantID:          o.TenantID,
		}

		if t, ok := tenantMap[o.TenantID]; ok {
			dto.TenantName = t.Name
		}

		items := itemsByOrder[o.ID]
		dto.Quantity = len(items)
		for _, item := range items {
			c := contentMap[item.ContentID]
			if c == nil {
				continue
			}
			ci := transaction_dto.ContentItem{
				ID:       c.ID,
				Title:    c.Title,
				Genre:    c.Genre,
				AuthorID: c.UserID,
				Price:    float64(item.AmountPaid) / 100.0,
			}
			for _, asset := range c.ContentAssets {
				if asset.Role == consts.ContentAssetRoleCover && asset.Asset != nil {
					ci.Cover = Common.GetAssetURL(asset.Asset.ObjectKey)
					break
				}
			}
			dto.Items = append(dto.Items, ci)
		}

		data = append(data, dto)
	}

	return data, nil
}

func (s *order) toUserOrderDTO(o *models.Order) user_dto.Order {
	return user_dto.Order{
		ID:         o.ID,
		Status:     string(o.Status), // Need cast for DTO string field if DTO field is string
		Amount:     float64(o.AmountPaid) / 100.0,
		CreateTime: o.CreatedAt.Format(time.RFC3339),
	}
}
