package services

import (
	"context"
	"errors"
	"strconv"
	"time"

	"quyun/v2/app/errorx"
	creator_dto "quyun/v2/app/http/v1/dto"
	"quyun/v2/app/requests"
	"quyun/v2/database/fields"
	"quyun/v2/database/models"
	"quyun/v2/pkg/consts"

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

// @provider
type creator struct{}

var genreMap = map[string]string{
	"Jingju":     "京剧",
	"Kunqu":      "昆曲",
	"Yueju":      "越剧",
	"Yuju":       "豫剧",
	"Huangmeixi": "黄梅戏",
	"Pingju":     "评剧",
	"Qinqiang":   "秦腔",
}

func (s *creator) Apply(ctx context.Context, tenantID, userID int64, form *creator_dto.ApplyForm) error {
	if userID == 0 {
		return errorx.ErrUnauthorized
	}
	uid := userID

	tbl, q := models.TenantQuery.QueryContext(ctx)
	// Check if already has a tenant
	count, _ := q.Where(tbl.UserID.Eq(uid)).Count()
	if count > 0 {
		return errorx.ErrBadRequest.WithMsg("您已是创作者")
	}

	// Create Tenant
	tenant := &models.Tenant{
		UserID: uid,
		Name:   form.Name,
		// Bio/Avatar in config
		Code:   uuid.NewString()[:8], // Generate random code
		UUID:   types.UUID(uuid.New()),
		Status: consts.TenantStatusPendingVerify,
	}

	if err := q.Create(tenant); err != nil {
		return errorx.ErrDatabaseError.WithCause(err)
	}

	// Also add user as tenant_admin in tenant_users
	tu := &models.TenantUser{
		TenantID: tenant.ID,
		UserID:   uid,
		Role:     types.Array[consts.TenantUserRole]{consts.TenantUserRoleTenantAdmin},
		Status:   consts.UserStatusVerified,
	}
	if err := models.TenantUserQuery.WithContext(ctx).Create(tu); err != nil {
		return errorx.ErrDatabaseError.WithCause(err)
	}

	return nil
}

func (s *creator) Dashboard(ctx context.Context, tenantID, userID int64) (*creator_dto.DashboardStats, error) {
	tid, err := s.getTenantID(ctx, tenantID, userID)
	if err != nil {
		return nil, err
	}

	// Followers: count tenant_users
	followers, _ := models.TenantUserQuery.WithContext(ctx).Where(models.TenantUserQuery.TenantID.Eq(tid)).Count()

	// Revenue: sum tenant_ledgers (income)
	var revenue float64
	// GORM doesn't have a direct Sum method in Gen yet easily accessible without raw SQL or result mapping
	// But we can use underlying DB
	models.TenantLedgerQuery.WithContext(ctx).UnderlyingDB().
		Model(&models.TenantLedger{}).
		Where("tenant_id = ? AND type = ?", tid, consts.TenantLedgerTypeDebitPurchase).
		Select("COALESCE(SUM(amount), 0)").
		Scan(&revenue)

	// Pending Refunds: count orders in refunding
	pendingRefunds, _ := models.OrderQuery.WithContext(ctx).
		Where(models.OrderQuery.TenantID.Eq(tid), models.OrderQuery.Status.Eq(consts.OrderStatusRefunding)).
		Count()

	stats := &creator_dto.DashboardStats{
		TotalFollowers: creator_dto.IntStatItem{Value: int(followers)},
		TotalRevenue:   creator_dto.FloatStatItem{Value: revenue / 100.0},
		PendingRefunds: int(pendingRefunds),
		NewMessages:    0,
	}
	return stats, nil
}

func (s *creator) ListContents(
	ctx context.Context,
	tenantID int64,
	userID int64,
	filter *creator_dto.CreatorContentListFilter,
) (*requests.Pager, error) {
	tid, err := s.getTenantID(ctx, tenantID, userID)
	if err != nil {
		return nil, err
	}

	tbl, q := models.ContentQuery.QueryContext(ctx)
	q = q.Where(tbl.TenantID.Eq(tid))

	if filter.Status != nil && *filter.Status != "" {
		q = q.Where(tbl.Status.Eq(consts.ContentStatus(*filter.Status)))
	}
	if filter.Visibility != nil && *filter.Visibility != "" {
		q = q.Where(tbl.Visibility.Eq(consts.ContentVisibility(*filter.Visibility)))
	}
	if filter.Genre != nil && *filter.Genre != "" {
		val := *filter.Genre
		if cn, ok := genreMap[val]; ok {
			q = q.Where(tbl.Genre.In(val, cn))
		} else {
			q = q.Where(tbl.Genre.Eq(val))
		}
	}
	if filter.Key != nil && *filter.Key != "" {
		q = q.Where(tbl.Key.Eq(*filter.Key))
	}
	if filter.Keyword != nil && *filter.Keyword != "" {
		q = q.Where(tbl.Title.Like("%" + *filter.Keyword + "%"))
	}

	// Pagination
	filter.Pagination.Format()
	total, err := q.Count()
	if err != nil {
		return nil, errorx.ErrDatabaseError.WithCause(err)
	}

	var list []*models.Content

	// Sorting
	sort := "latest"
	if filter.Sort != nil && *filter.Sort != "" {
		sort = *filter.Sort
	}
	switch sort {
	case "oldest":
		q = q.Order(tbl.ID.Asc())
	case "views":
		q = q.Order(tbl.Views.Desc())
	case "likes":
		q = q.Order(tbl.Likes.Desc())
	default:
		q = q.Order(tbl.ID.Desc())
	}

	err = q.Offset(int(filter.Pagination.Offset())).Limit(int(filter.Pagination.Limit)).
		UnderlyingDB().
		Preload("ContentAssets").
		Preload("ContentAssets.Asset").
		Find(&list).Error
	if err != nil {
		return nil, errorx.ErrDatabaseError.WithCause(err)
	}

	// Fetch Prices
	priceMap := make(map[int64]float64)
	if len(list) > 0 {
		ids := make([]int64, len(list))
		for i, item := range list {
			ids[i] = item.ID
		}
		pTbl, pQ := models.ContentPriceQuery.QueryContext(ctx)
		prices, _ := pQ.Where(pTbl.ContentID.In(ids...)).Find()
		for _, p := range prices {
			priceMap[p.ContentID] = float64(p.PriceAmount) / 100.0
		}
	}

	var data []creator_dto.CreatorContentItem
	for _, item := range list {
		var imageCount, videoCount, audioCount int
		var cover string
		var firstImage string

		for _, ca := range item.ContentAssets {
			if ca.Asset == nil {
				continue
			}

			// Count logic
			switch ca.Asset.Type {
			case consts.MediaAssetTypeImage:
				imageCount++
				if firstImage == "" {
					firstImage = Common.GetAssetURL(ca.Asset.ObjectKey)
				}
			case consts.MediaAssetTypeVideo:
				videoCount++
			case consts.MediaAssetTypeAudio:
				audioCount++
			}

			// Cover logic
			if ca.Role == consts.ContentAssetRoleCover && cover == "" {
				cover = Common.GetAssetURL(ca.Asset.ObjectKey)
			}
		}

		if cover == "" {
			cover = firstImage
		}

		data = append(data, creator_dto.CreatorContentItem{
			ID:          item.ID,
			Title:       item.Title,
			Genre:       item.Genre,
			Key:         item.Key,
			Price:       priceMap[item.ID],
			Views:       int(item.Views),
			Likes:       int(item.Likes),
			Cover:       cover,
			ImageCount:  imageCount,
			VideoCount:  videoCount,
			AudioCount:  audioCount,
			Status:      string(item.Status),
			Visibility:  string(item.Visibility),
			CreatedAt:   item.CreatedAt.Format("2006-01-02 15:04"),
			IsPinned:    item.IsPinned,
			IsPurchased: false,
		})
	}

	return &requests.Pager{
		Pagination: filter.Pagination,
		Total:      total,
		Items:      data,
	}, nil
}

func (s *creator) CreateContent(ctx context.Context, tenantID, userID int64, form *creator_dto.ContentCreateForm) error {
	tid, err := s.getTenantID(ctx, tenantID, userID)
	if err != nil {
		return err
	}
	uid := userID

	return models.Q.Transaction(func(tx *models.Query) error {
		status := consts.ContentStatusPublished
		if form.Status != "" {
			status = consts.ContentStatus(form.Status)
		}
		// 校验素材归属，避免跨租户引用。
		if err := s.validateContentAssets(ctx, tx, tid, uid, form.CoverIDs, form.MediaIDs); err != nil {
			return err
		}

		// 1. Create Content
		content := &models.Content{
			TenantID: tid,
			UserID:   uid,
			Title:    form.Title,
			Genre:    form.Genre,
			Key:      form.Key,
			Status:   status,
		}
		if err := tx.Content.WithContext(ctx).Create(content); err != nil {
			return err
		}

		// 2. Link Assets
		var assets []*models.ContentAsset
		// Covers
		for i, mid := range form.CoverIDs {
			assets = append(assets, &models.ContentAsset{
				TenantID:  tid,
				UserID:    uid,
				ContentID: content.ID,
				AssetID:   mid,
				Sort:      int32(i),
				Role:      consts.ContentAssetRoleCover,
			})
		}
		// Main Media
		for i, mid := range form.MediaIDs {
			assets = append(assets, &models.ContentAsset{
				TenantID:  tid,
				UserID:    uid,
				ContentID: content.ID,
				AssetID:   mid,
				Sort:      int32(i),
				Role:      consts.ContentAssetRoleMain,
			})
		}
		if len(assets) > 0 {
			if err := tx.ContentAsset.WithContext(ctx).Create(assets...); err != nil {
				return err
			}
		}

		// 3. Set Price
		price := &models.ContentPrice{
			TenantID:    tid,
			UserID:      uid,
			ContentID:   content.ID,
			PriceAmount: int64(form.Price * 100), // Convert to cents
			Currency:    consts.CurrencyCNY,
		}
		if err := tx.ContentPrice.WithContext(ctx).Create(price); err != nil {
			return err
		}

		return nil
	})
}

func (s *creator) UpdateContent(
	ctx context.Context,
	tenantID int64,
	userID int64,
	id int64,
	form *creator_dto.ContentUpdateForm,
) error {
	tid, err := s.getTenantID(ctx, tenantID, userID)
	if err != nil {
		return err
	}
	uid := userID

	return models.Q.Transaction(func(tx *models.Query) error {
		// 1. Check Ownership
		c, err := tx.Content.WithContext(ctx).Where(tx.Content.ID.Eq(id), tx.Content.TenantID.Eq(tid)).First()
		if err != nil {
			return errorx.ErrRecordNotFound
		}

		// 2. Update Content
		contentUpdates := &models.Content{
			Title: form.Title,
			Genre: form.Genre,
			Key:   form.Key,
		}
		if form.Status != "" {
			contentUpdates.Status = consts.ContentStatus(form.Status)
		}

		// Determine final status
		finalStatus := c.Status
		if form.Status != "" {
			finalStatus = consts.ContentStatus(form.Status)
		}

		// Validation: Only published content can be pinned
		if form.IsPinned != nil && *form.IsPinned && finalStatus != consts.ContentStatusPublished {
			return errorx.ErrBadRequest.WithMsg("只有已发布的内容支持置顶")
		}

		// Perform standard updates
		_, err = tx.Content.WithContext(ctx).Where(tx.Content.ID.Eq(id)).Updates(contentUpdates)
		if err != nil {
			return err
		}

		// Handle IsPinned Logic
		if finalStatus != consts.ContentStatusPublished {
			// Force Unpin if not published
			_, err = tx.Content.WithContext(ctx).Where(tx.Content.ID.Eq(id)).UpdateSimple(tx.Content.IsPinned.Value(false))
			if err != nil {
				return err
			}
		} else if form.IsPinned != nil {
			// Explicit Pin Update requested
			_, err = tx.Content.WithContext(ctx).Where(tx.Content.ID.Eq(id)).UpdateSimple(tx.Content.IsPinned.Value(*form.IsPinned))
			if err != nil {
				return err
			}

			// If setting to true, unpin others
			if *form.IsPinned {
				if _, err := tx.Content.WithContext(ctx).
					Where(tx.Content.TenantID.Eq(tid), tx.Content.ID.Neq(id)).
					UpdateSimple(tx.Content.IsPinned.Value(false)); err != nil {
					return err
				}
			}
		}

		// 3. Update Price
		// Check if price exists
		if form.Price != nil {
			count, _ := tx.ContentPrice.WithContext(ctx).Where(tx.ContentPrice.ContentID.Eq(id)).Count()
			newPrice := int64(*form.Price * 100)
			if count > 0 {
				_, err = tx.ContentPrice.WithContext(ctx).
					Where(tx.ContentPrice.ContentID.Eq(id)).
					UpdateSimple(tx.ContentPrice.PriceAmount.Value(newPrice))
			} else {
				err = tx.ContentPrice.WithContext(ctx).Create(&models.ContentPrice{
					TenantID:    tid,
					UserID:      c.UserID,
					ContentID:   id,
					PriceAmount: newPrice,
					Currency:    consts.CurrencyCNY,
				})
			}
			if err != nil {
				return err
			}
		}

		// 4. Update Assets (Full replacement strategy)
		if err := s.validateContentAssets(ctx, tx, tid, uid, form.CoverIDs, form.MediaIDs); err != nil {
			return err
		}

		_, err = tx.ContentAsset.WithContext(ctx).Where(tx.ContentAsset.ContentID.Eq(id)).Delete()
		if err != nil {
			return err
		}

		var assets []*models.ContentAsset
		// Covers
		for i, mid := range form.CoverIDs {
			assets = append(assets, &models.ContentAsset{
				TenantID:  tid,
				UserID:    uid,
				ContentID: id,
				AssetID:   mid,
				Sort:      int32(i),
				Role:      consts.ContentAssetRoleCover,
			})
		}
		// Main Media
		for i, mid := range form.MediaIDs {
			assets = append(assets, &models.ContentAsset{
				TenantID:  tid,
				UserID:    uid,
				ContentID: id,
				AssetID:   mid,
				Sort:      int32(i),
				Role:      consts.ContentAssetRoleMain,
			})
		}
		if len(assets) > 0 {
			if err := tx.ContentAsset.WithContext(ctx).Create(assets...); err != nil {
				return err
			}
		}

		return nil
	})
}

func (s *creator) DeleteContent(ctx context.Context, tenantID, userID, id int64) error {
	tid, err := s.getTenantID(ctx, tenantID, userID)
	if err != nil {
		return err
	}

	// Check if purchased (ContentAccess exists)
	count, _ := models.ContentAccessQuery.WithContext(ctx).Where(models.ContentAccessQuery.ContentID.Eq(id)).Count()
	if count > 0 {
		return errorx.ErrPreconditionFailed.WithMsg("该内容已被购买，无法删除")
	}

	_, err = models.ContentQuery.WithContext(ctx).
		Where(models.ContentQuery.ID.Eq(id), models.ContentQuery.TenantID.Eq(tid)).
		Delete()
	if err != nil {
		return errorx.ErrDatabaseError.WithCause(err)
	}
	return nil
}

func (s *creator) GetContent(ctx context.Context, tenantID, userID, id int64) (*creator_dto.ContentEditDTO, error) {
	tid, err := s.getTenantID(ctx, tenantID, userID)
	if err != nil {
		return nil, err
	}

	// Fetch Content with preloads
	var c models.Content
	err = models.ContentQuery.WithContext(ctx).
		Where(models.ContentQuery.ID.Eq(id), models.ContentQuery.TenantID.Eq(tid)).
		UnderlyingDB().
		Preload("ContentAssets", func(db *gorm.DB) *gorm.DB {
			return db.Order("sort ASC")
		}).
		Preload("ContentAssets.Asset").
		First(&c).Error
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, errorx.ErrRecordNotFound
		}
		return nil, errorx.ErrDatabaseError.WithCause(err)
	}

	// Fetch Price
	var price float64
	cp, err := models.ContentPriceQuery.WithContext(ctx).Where(models.ContentPriceQuery.ContentID.Eq(id)).First()
	if err == nil {
		price = float64(cp.PriceAmount) / 100.0
	}

	dto := &creator_dto.ContentEditDTO{
		ID:             c.ID,
		Title:          c.Title,
		Genre:          c.Genre,
		Key:            c.Key,
		Description:    c.Description,
		Status:         string(c.Status),
		Price:          price,
		EnableTrial:    c.PreviewSeconds > 0,
		PreviewSeconds: int(c.PreviewSeconds),
		Assets:         make([]creator_dto.AssetDTO, 0),
	}

	for _, ca := range c.ContentAssets {
		if ca.Asset != nil {
			meta := ca.Asset.Meta.Data()
			name := meta.Filename
			if name == "" {
				// Fallback: strip UUID prefix (36 chars + 1 underscore = 37)
				if len(ca.Asset.ObjectKey) > 37 && ca.Asset.ObjectKey[36] == '_' {
					name = ca.Asset.ObjectKey[37:]
				} else {
					name = ca.Asset.ObjectKey
				}
			}

			sizeBytes := meta.Size
			sizeMB := float64(sizeBytes) / 1024.0 / 1024.0
			sizeStr := strconv.FormatFloat(float64(int(sizeMB*100))/100.0, 'f', -1, 64) + " MB"

			dto.Assets = append(dto.Assets, creator_dto.AssetDTO{
				ID:   ca.AssetID,
				Role: string(ca.Role),
				Type: string(ca.Asset.Type),
				URL:  Common.GetAssetURL(ca.Asset.ObjectKey),
				Name: name,
				Size: sizeStr,
				Sort: int(ca.Sort),
			})
		}
	}
	return dto, nil
}

func (s *creator) ListOrders(
	ctx context.Context,
	tenantID int64,
	userID int64,
	filter *creator_dto.CreatorOrderListFilter,
) ([]creator_dto.Order, error) {
	tid, err := s.getTenantID(ctx, tenantID, userID)
	if err != nil {
		return nil, err
	}

	tbl, q := models.OrderQuery.QueryContext(ctx)
	q = q.Where(tbl.TenantID.Eq(tid))

	if filter.Status != nil && *filter.Status != "" {
		q = q.Where(tbl.Status.Eq(consts.OrderStatus(*filter.Status)))
	}

	if filter.Keyword != nil && *filter.Keyword != "" {
		k := *filter.Keyword
		if id, err := strconv.ParseInt(k, 10, 64); err == nil {
			q = q.Where(tbl.ID.Eq(id))
		} else {
			uTbl, uQ := models.UserQuery.QueryContext(ctx)
			users, _ := uQ.Where(uTbl.Nickname.Like("%" + k + "%")).Find()
			uids := make([]int64, len(users))
			for i, u := range users {
				uids[i] = u.ID
			}
			if len(uids) > 0 {
				q = q.Where(tbl.UserID.In(uids...))
			} else {
				q = q.Where(tbl.ID.Eq(-1)) // Match nothing
			}
		}
	}

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

	if len(list) == 0 {
		return []creator_dto.Order{}, nil
	}

	// 批量加载买家、订单明细、内容，避免列表场景 N+1 查询。
	orderIDs := make([]int64, 0, len(list))
	userSet := make(map[int64]struct{}, len(list))
	for _, o := range list {
		orderIDs = append(orderIDs, o.ID)
		if o.UserID > 0 {
			userSet[o.UserID] = struct{}{}
		}
	}

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

	userMap := make(map[int64]*models.User, len(userIDs))
	if len(userIDs) > 0 {
		uTbl, uQ := models.UserQuery.QueryContext(ctx)
		users, err := uQ.Where(uTbl.ID.In(userIDs...)).Find()
		if err != nil {
			return nil, errorx.ErrDatabaseError.WithCause(err)
		}
		for _, u := range users {
			userMap[u.ID] = u
		}
	}

	itemMap := make(map[int64]*models.OrderItem, len(orderIDs))
	contentSet := make(map[int64]struct{}, len(orderIDs))
	if len(orderIDs) > 0 {
		itemTbl, itemQ := models.OrderItemQuery.QueryContext(ctx)
		items, err := itemQ.Where(itemTbl.OrderID.In(orderIDs...)).
			Order(itemTbl.OrderID.Asc(), itemTbl.ID.Asc()).
			Find()
		if err != nil {
			return nil, errorx.ErrDatabaseError.WithCause(err)
		}
		for _, item := range items {
			if _, exists := itemMap[item.OrderID]; exists {
				continue
			}
			itemMap[item.OrderID] = item
			if item.ContentID > 0 {
				contentSet[item.ContentID] = struct{}{}
			}
		}
	}

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

	contentMap := make(map[int64]*models.Content, len(contentIDs))
	if len(contentIDs) > 0 {
		var contents []*models.Content
		cTbl, cQ := models.ContentQuery.QueryContext(ctx)
		err := cQ.Where(cTbl.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([]creator_dto.Order, 0, len(list))
	for _, o := range list {
		buyerName := "未知用户"
		buyerAvatar := ""
		if u := userMap[o.UserID]; u != nil {
			buyerName = u.Nickname
			buyerAvatar = u.Avatar
		}

		var title, cover string
		if item := itemMap[o.ID]; item != nil {
			if c := contentMap[item.ContentID]; c != nil {
				title = c.Title
				for _, ca := range c.ContentAssets {
					if ca.Role == consts.ContentAssetRoleCover && ca.Asset != nil {
						cover = Common.GetAssetURL(ca.Asset.ObjectKey)
						break
					}
				}
			}
		}

		data = append(data, creator_dto.Order{
			ID:          o.ID,
			Status:      string(o.Status),
			Amount:      float64(o.AmountPaid) / 100.0,
			CreateTime:  o.CreatedAt.Format(time.RFC3339),
			BuyerName:   buyerName,
			BuyerAvatar: buyerAvatar,
			Title:       title,
			Cover:       cover,
		})
	}
	return data, nil
}

func (s *creator) ProcessRefund(ctx context.Context, tenantID, userID, id int64, form *creator_dto.RefundForm) error {
	tid, err := s.getTenantID(ctx, tenantID, userID)
	if err != nil {
		return err
	}
	uid := userID // Creator ID

	// Fetch Order
	o, err := models.OrderQuery.WithContext(ctx).
		Where(models.OrderQuery.ID.Eq(id), models.OrderQuery.TenantID.Eq(tid)).
		First()
	if err != nil {
		return errorx.ErrRecordNotFound
	}

	// Validate Status
	// Allow refunding 'refunding' orders. Or 'paid' if we treat this as "Initiate Refund".
	// Given "Action" (accept/reject), assume 'refunding'.
	if o.Status != consts.OrderStatusRefunding {
		return errorx.ErrStatusConflict.WithMsg("订单状态不是退款中")
	}

	if form.Action == "reject" {
		_, err := models.OrderQuery.WithContext(ctx).Where(models.OrderQuery.ID.Eq(id)).Updates(&models.Order{
			Status:       consts.OrderStatusPaid,
			RefundReason: form.Reason, // Store reject reason? Or clear it?
		})
		return err
	}

	if form.Action == "accept" {
		// 关键退款事务：遇到数据库冲突/死锁时短暂退避重试，避免退款卡住。
		return retryCriticalWrite(ctx, func() error {
			return models.Q.Transaction(func(tx *models.Query) error {
				// 1. Deduct Creator Balance
				// We credited Creator User Balance in Order.Pay. Now deduct it.
				info, err := tx.User.WithContext(ctx).
					Where(tx.User.ID.Eq(uid), 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. Credit Buyer 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
				}

				// 3. Update Order Status
				_, err = tx.Order.WithContext(ctx).Where(tx.Order.ID.Eq(id)).Updates(&models.Order{
					Status:               consts.OrderStatusRefunded,
					RefundedAt:           time.Now(),
					RefundOperatorUserID: uid,
					RefundReason:         form.Reason,
				})
				if err != nil {
					return err
				}

				// 4. Revoke Content Access
				// Fetch order items to get content IDs
				items, _ := tx.OrderItem.WithContext(ctx).Where(tx.OrderItem.OrderID.Eq(o.ID)).Find()
				contentIDs := make([]int64, len(items))
				for i, item := range items {
					contentIDs[i] = item.ContentID
				}
				if len(contentIDs) > 0 {
					_, err = tx.ContentAccess.WithContext(ctx).
						Where(tx.ContentAccess.UserID.Eq(o.UserID), tx.ContentAccess.ContentID.In(contentIDs...)).
						UpdateSimple(tx.ContentAccess.Status.Value(consts.ContentAccessStatusRevoked))
					if err != nil {
						return err
					}
				}

				// 5. Create Tenant Ledger
				ledger := &models.TenantLedger{
					TenantID:       tid,
					UserID:         uid,
					OrderID:        o.ID,
					Type:           consts.TenantLedgerTypeCreditRefund,
					Amount:         o.AmountPaid,
					Remark:         "退款: " + form.Reason,
					OperatorUserID: uid,
					IdempotencyKey: uuid.NewString(),
				}
				if err := tx.TenantLedger.WithContext(ctx).Create(ledger); err != nil {
					return err
				}

				return nil
			})
		})
	}

	return errorx.ErrBadRequest.WithMsg("无效的操作")
}

func (s *creator) GetSettings(ctx context.Context, tenantID, userID int64) (*creator_dto.Settings, error) {
	tid, err := s.getTenantID(ctx, tenantID, userID)
	if err != nil {
		return nil, err
	}
	t, err := models.TenantQuery.WithContext(ctx).Where(models.TenantQuery.ID.Eq(tid)).First()
	if err != nil {
		return nil, errorx.ErrRecordNotFound
	}
	cfg := t.Config.Data()
	return &creator_dto.Settings{
		ID:          t.ID,
		Name:        t.Name,
		Bio:         cfg.Bio,
		Avatar:      cfg.Avatar,
		Cover:       cfg.Cover,
		Description: cfg.Description,
	}, nil
}

func (s *creator) UpdateSettings(ctx context.Context, tenantID, userID int64, form *creator_dto.Settings) error {
	tid, err := s.getTenantID(ctx, tenantID, userID)
	if err != nil {
		return err
	}

	t, err := models.TenantQuery.WithContext(ctx).Where(models.TenantQuery.ID.Eq(tid)).First()
	if err != nil {
		return errorx.ErrRecordNotFound
	}

	cfg := t.Config.Data()
	cfg.Bio = form.Bio
	cfg.Avatar = form.Avatar
	cfg.Cover = form.Cover
	cfg.Description = form.Description

	_, err = models.TenantQuery.WithContext(ctx).Where(models.TenantQuery.ID.Eq(tid)).Updates(&models.Tenant{
		Name:   form.Name,
		Config: types.NewJSONType(cfg),
	})
	return err
}

func (s *creator) ListPayoutAccounts(ctx context.Context, tenantID, userID int64) ([]creator_dto.PayoutAccount, error) {
	tid, err := s.getTenantID(ctx, tenantID, userID)
	if err != nil {
		return nil, err
	}

	list, err := models.PayoutAccountQuery.WithContext(ctx).Where(models.PayoutAccountQuery.TenantID.Eq(tid)).Find()
	if err != nil {
		return nil, errorx.ErrDatabaseError.WithCause(err)
	}

	var data []creator_dto.PayoutAccount
	for _, v := range list {
		data = append(data, creator_dto.PayoutAccount{
			ID:       v.ID,
			Type:     v.Type,
			Name:     v.Name,
			Account:  v.Account,
			Realname: v.Realname,
		})
	}
	return data, nil
}

func (s *creator) AddPayoutAccount(ctx context.Context, tenantID, userID int64, form *creator_dto.PayoutAccount) error {
	tid, err := s.getTenantID(ctx, tenantID, userID)
	if err != nil {
		return err
	}
	uid := userID

	pa := &models.PayoutAccount{
		TenantID: tid,
		UserID:   uid,
		Type:     form.Type,
		Name:     form.Name,
		Account:  form.Account,
		Realname: form.Realname,
	}
	if err := models.PayoutAccountQuery.WithContext(ctx).Create(pa); err != nil {
		return errorx.ErrDatabaseError.WithCause(err)
	}
	return nil
}

func (s *creator) RemovePayoutAccount(ctx context.Context, tenantID, userID, id int64) error {
	tid, err := s.getTenantID(ctx, tenantID, userID)
	if err != nil {
		return err
	}

	_, err = models.PayoutAccountQuery.WithContext(ctx).
		Where(models.PayoutAccountQuery.ID.Eq(id), models.PayoutAccountQuery.TenantID.Eq(tid)).
		Delete()
	if err != nil {
		return errorx.ErrDatabaseError.WithCause(err)
	}
	return nil
}

func (s *creator) Withdraw(ctx context.Context, tenantID, userID int64, form *creator_dto.WithdrawForm) error {
	tid, err := s.getTenantID(ctx, tenantID, userID)
	if err != nil {
		return err
	}
	uid := userID

	// Validate User Real-name Status
	user, err := models.UserQuery.WithContext(ctx).Where(models.UserQuery.ID.Eq(uid)).First()
	if err != nil {
		return errorx.ErrDatabaseError.WithCause(err)
	}
	if !user.IsRealNameVerified {
		return errorx.ErrPreconditionFailed.WithMsg("请先完成实名认证后再申请提现")
	}

	amount := int64(form.Amount * 100)
	if amount <= 0 {
		return errorx.ErrBadRequest.WithMsg("金额无效")
	}

	// Validate Payout Account
	_, err = models.PayoutAccountQuery.WithContext(ctx).
		Where(models.PayoutAccountQuery.ID.Eq(form.AccountID), models.PayoutAccountQuery.TenantID.Eq(tid)).
		First()
	if err != nil {
		return errorx.ErrRecordNotFound.WithMsg("收款账户不存在")
	}

	return models.Q.Transaction(func(tx *models.Query) error {
		// 1. Deduct Balance
		info, err := tx.User.WithContext(ctx).
			Where(tx.User.ID.Eq(uid), tx.User.Balance.Gte(amount)).
			Update(tx.User.Balance, gorm.Expr("balance - ?", amount))
		if err != nil {
			return err
		}
		if info.RowsAffected == 0 {
			return errorx.ErrQuotaExceeded.WithMsg("余额不足")
		}

		// 2. Create Order (Withdrawal)
		order := &models.Order{
			TenantID:       tid,
			UserID:         uid,
			Type:           consts.OrderTypeWithdrawal,
			Status:         consts.OrderStatusCreated, // Created = Pending Processing
			Currency:       consts.CurrencyCNY,
			AmountOriginal: amount,
			AmountPaid:     amount, // Actually Amount Withdrawn
			IdempotencyKey: uuid.NewString(),
			Snapshot:       types.NewJSONType(fields.OrdersSnapshot{}), // Can store account details here
		}
		if err := tx.Order.WithContext(ctx).Create(order); err != nil {
			return err
		}

		// 3. Create Tenant Ledger
		ledger := &models.TenantLedger{
			TenantID:       tid,
			UserID:         uid,
			OrderID:        order.ID,
			Type:           consts.TenantLedgerTypeCreditWithdrawal,
			Amount:         amount,
			Remark:         "提现申请",
			OperatorUserID: uid,
			IdempotencyKey: uuid.NewString(),
		}
		if err := tx.TenantLedger.WithContext(ctx).Create(ledger); err != nil {
			return err
		}

		return nil
	})
}

// Helpers

func (s *creator) getTenantID(ctx context.Context, tenantID, userID int64) (int64, error) {
	if userID == 0 {
		return 0, errorx.ErrUnauthorized
	}
	uid := userID

	// Simple check: User owns tenant
	t, err := models.TenantQuery.WithContext(ctx).Where(models.TenantQuery.UserID.Eq(uid)).First()
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return 0, errorx.ErrPermissionDenied.WithMsg("非创作者")
		}
		return 0, errorx.ErrDatabaseError.WithCause(err)
	}
	if tenantID > 0 && t.ID != tenantID {
		return 0, errorx.ErrPermissionDenied.WithMsg("无权限访问该租户")
	}
	return t.ID, nil
}

func (s *creator) validateContentAssets(
	ctx context.Context,
	tx *models.Query,
	tenantID int64,
	userID int64,
	coverIDs []int64,
	mediaIDs []int64,
) error {
	if len(coverIDs) == 0 && len(mediaIDs) == 0 {
		return nil
	}

	ids := make(map[int64]struct{}, len(coverIDs)+len(mediaIDs))
	for _, id := range coverIDs {
		ids[id] = struct{}{}
	}
	for _, id := range mediaIDs {
		ids[id] = struct{}{}
	}
	if len(ids) == 0 {
		return nil
	}

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

	list, err := tx.MediaAsset.WithContext(ctx).Where(tx.MediaAsset.ID.In(assetIDs...)).Find()
	if err != nil {
		return errorx.ErrDatabaseError.WithCause(err)
	}
	if len(list) != len(assetIDs) {
		return errorx.ErrRecordNotFound.WithMsg("素材不存在")
	}

	for _, asset := range list {
		if asset.TenantID == tenantID {
			continue
		}
		if asset.TenantID == 0 && asset.UserID == userID {
			continue
		}
		return errorx.ErrForbidden.WithMsg("素材不属于当前租户")
	}

	return nil
}
