package services

import (
	"context"
	"errors"

	"quyun/v2/app/errorx"
	content_dto "quyun/v2/app/http/v1/dto"
	user_dto "quyun/v2/app/http/v1/dto"
	"quyun/v2/app/requests"
	"quyun/v2/database/models"
	"quyun/v2/pkg/consts"

	"go.ipao.vip/gen/field"
	"go.ipao.vip/gen/types"
	"gorm.io/gorm"
)

// @provider
type content struct{}

func (s *content) List(ctx context.Context, tenantID int64, filter *content_dto.ContentListFilter) (*requests.Pager, error) {
	tbl, q := models.ContentQuery.QueryContext(ctx)

	// Filters
	if filter.Keyword != nil && *filter.Keyword != "" {
		keyword := "%" + *filter.Keyword + "%"
		q = q.Where(field.Or(tbl.Title.Like(keyword), tbl.Description.Like(keyword)))
	}
	q = q.Where(tbl.Status.Eq(consts.ContentStatusPublished))
	if tenantID > 0 {
		q = q.Where(tbl.TenantID.Eq(tenantID))
	}
	// 私密内容不参与公开列表展示。
	q = q.Where(tbl.Visibility.Neq(consts.ContentVisibilityPrivate))
	if filter.AuthorID != nil && *filter.AuthorID > 0 {
		q = q.Where(tbl.UserID.Eq(*filter.AuthorID))
	}
	if filter.Genre != nil && *filter.Genre != "" {
		q = q.Where(tbl.Genre.Eq(*filter.Genre))
	}
	if filter.TenantID != nil && *filter.TenantID > 0 {
		if tenantID > 0 && *filter.TenantID != tenantID {
			return nil, errorx.ErrForbidden.WithMsg("租户不匹配")
		}
		q = q.Where(tbl.TenantID.Eq(*filter.TenantID))
	}
	if filter.IsPinned != nil {
		q = q.Where(tbl.IsPinned.Is(*filter.IsPinned))
	}

	if filter.PriceType != nil && *filter.PriceType != "all" {
		if *filter.PriceType == "member" {
			q = q.Where(tbl.Visibility.Eq(consts.ContentVisibilityTenantOnly))
		} else {
			pTbl, pQ := models.ContentPriceQuery.QueryContext(ctx)

			var shouldFilter bool
			var prices []*models.ContentPrice

			if *filter.PriceType == "free" {
				shouldFilter = true
				prices, _ = pQ.Where(pTbl.PriceAmount.Eq(0)).Select(pTbl.ContentID).Find()
			} else if *filter.PriceType == "paid" {
				shouldFilter = true
				prices, _ = pQ.Where(pTbl.PriceAmount.Gt(0)).Select(pTbl.ContentID).Find()
			}

			if shouldFilter {
				ids := make([]int64, len(prices))
				for i, p := range prices {
					ids[i] = p.ContentID
				}
				if len(ids) > 0 {
					q = q.Where(tbl.ID.In(ids...))
				} else {
					q = q.Where(tbl.ID.Eq(-1))
				}
			}
		}
	}

	// Sort
	sort := "latest"
	if filter.Sort != nil && *filter.Sort != "" {
		sort = *filter.Sort
	}

	switch sort {
	case "hot":
		q = q.Order(tbl.Views.Desc())
	case "price_asc":
		q = q.Order(tbl.ID.Desc())
	default: // latest
		q = q.Order(tbl.ID.Desc())
	}

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

	// Use UnderlyingDB for complex preloads
	var list []*models.Content
	err = q.Offset(int(filter.Pagination.Offset())).Limit(int(filter.Pagination.Limit)).
		UnderlyingDB().
		Preload("Author").
		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
		}
	}

	// Convert to DTO
	data := make([]content_dto.ContentItem, len(list))
	for i, item := range list {
		data[i] = s.toContentItemDTO(item, priceMap[item.ID], false)
	}

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

func (s *content) Get(ctx context.Context, tenantID, userID, id int64) (*content_dto.ContentDetail, error) {
	_, q := models.ContentQuery.QueryContext(ctx)

	var item models.Content
	db := q.UnderlyingDB()
	if tenantID > 0 {
		db = db.Where("tenant_id = ?", tenantID)
	}
	err := db.
		Preload("Author").
		Preload("ContentAssets", func(db *gorm.DB) *gorm.DB {
			return db.Order("sort ASC")
		}).
		Preload("ContentAssets.Asset").
		Where("id = ?", id).
		First(&item).Error
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, errorx.ErrRecordNotFound
		}
		return nil, errorx.ErrDatabaseError.WithCause(err)
	}

	if err := s.ensureContentReadable(ctx, userID, &item); err != nil {
		return nil, err
	}

	// Increment Views
	update := models.ContentQuery.WithContext(ctx).Where(models.ContentQuery.ID.Eq(id))
	if tenantID > 0 {
		update = update.Where(models.ContentQuery.TenantID.Eq(tenantID))
	}
	_, _ = update.UpdateSimple(models.ContentQuery.Views.Add(1))

	// 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
	}

	// Interaction & Access status
	isLiked := false
	isFavorited := false
	hasAccess := false

	if userID > 0 {
		uid := userID
		// Interaction
		isLiked, _ = models.UserContentActionQuery.WithContext(ctx).
			Where(models.UserContentActionQuery.UserID.Eq(uid),
				models.UserContentActionQuery.ContentID.Eq(id),
				models.UserContentActionQuery.Type.Eq("like")).
			Exists()
		isFavorited, _ = models.UserContentActionQuery.WithContext(ctx).
			Where(models.UserContentActionQuery.UserID.Eq(uid),
				models.UserContentActionQuery.ContentID.Eq(id),
				models.UserContentActionQuery.Type.Eq("favorite")).
			Exists()

		// Access Check
		if item.UserID == uid {
			hasAccess = true // Owner
		} else {
			isAdmin, err := s.isTenantAdmin(ctx, item.TenantID, uid)
			if err != nil {
				return nil, err
			}
			if isAdmin {
				hasAccess = true
			}
		}

		if !hasAccess {
			// Check Purchase
			exists, err := models.ContentAccessQuery.WithContext(ctx).
				Where(models.ContentAccessQuery.UserID.Eq(uid),
					models.ContentAccessQuery.ContentID.Eq(id),
					models.ContentAccessQuery.Status.Eq(consts.ContentAccessStatusActive)).
				Exists()
			if err != nil {
				return nil, errorx.ErrDatabaseError.WithCause(err)
			}
			if exists {
				hasAccess = true
			}
		}

		if !hasAccess && item.Visibility == consts.ContentVisibilityTenantOnly {
			isMember, err := s.isTenantMember(ctx, item.TenantID, uid)
			if err != nil {
				return nil, err
			}
			if isMember {
				hasAccess = true
			}
		}
	}

	// Filter Assets based on Access
	var accessibleAssets []*models.ContentAsset
	for _, ca := range item.ContentAssets {
		if hasAccess {
			accessibleAssets = append(accessibleAssets, ca)
		} else {
			// If no access, only allow Preview and Cover
			if ca.Role == consts.ContentAssetRolePreview || ca.Role == consts.ContentAssetRoleCover {
				accessibleAssets = append(accessibleAssets, ca)
			}
		}
	}

	// Check if author is followed
	authorIsFollowing := false
	if userID > 0 {
		exists, _ := models.TenantUserQuery.WithContext(ctx).
			Where(models.TenantUserQuery.TenantID.Eq(item.TenantID),
				models.TenantUserQuery.UserID.Eq(userID),
				models.TenantUserQuery.Role.Contains(types.Array[consts.TenantUserRole]{consts.TenantUserRoleMember})).
			Exists()
		authorIsFollowing = exists
	}

	detail := &content_dto.ContentDetail{
		ContentItem: s.toContentItemDTO(&item, price, authorIsFollowing),
		Description: item.Description,
		Body:        item.Body,
		MediaUrls:   s.toMediaURLs(accessibleAssets),
		Meta:        content_dto.Meta{Key: item.Key},
		IsLiked:     isLiked,
		IsFavorited: isFavorited,
	}
	// Pass IsPurchased/HasAccess info to frontend?
	detail.ContentItem.IsPurchased = hasAccess // 购买或具备可访问权限时统一展示为已解锁。

	return detail, nil
}

func (s *content) ListComments(ctx context.Context, tenantID, userID, id int64, page int) (*requests.Pager, error) {
	contentQuery := models.ContentQuery.WithContext(ctx).Where(models.ContentQuery.ID.Eq(id))
	if tenantID > 0 {
		contentQuery = contentQuery.Where(models.ContentQuery.TenantID.Eq(tenantID))
	}
	content, err := contentQuery.First()
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, errorx.ErrRecordNotFound
		}
		return nil, errorx.ErrDatabaseError.WithCause(err)
	}
	if err := s.ensureContentReadable(ctx, userID, content); err != nil {
		return nil, err
	}

	tbl, q := models.CommentQuery.QueryContext(ctx)

	q = q.Where(tbl.ContentID.Eq(id)).Preload(tbl.User)
	if tenantID > 0 {
		q = q.Where(tbl.TenantID.Eq(tenantID))
	}
	q = q.Order(tbl.CreatedAt.Desc())

	p := requests.Pagination{Page: int64(page), Limit: 10}
	total, err := q.Count()
	if err != nil {
		return nil, errorx.ErrDatabaseError.WithCause(err)
	}

	list, err := q.Offset(int(p.Offset())).Limit(int(p.Limit)).Find()
	if err != nil {
		return nil, errorx.ErrDatabaseError.WithCause(err)
	}

	// User likes
	likedMap := make(map[int64]bool)
	if userID > 0 {
		uid := userID
		ids := make([]int64, len(list))
		for i, v := range list {
			ids[i] = v.ID
		}
		likes, _ := models.UserCommentActionQuery.WithContext(ctx).
			Where(models.UserCommentActionQuery.UserID.Eq(uid),
				models.UserCommentActionQuery.CommentID.In(ids...),
				models.UserCommentActionQuery.Type.Eq("like")).
			Find()
		for _, l := range likes {
			likedMap[l.CommentID] = true
		}
	}

	data := make([]content_dto.Comment, len(list))
	for i, v := range list {
		data[i] = content_dto.Comment{
			ID:           v.ID,
			Content:      v.Content,
			UserID:       v.UserID,
			UserNickname: v.User.Nickname,
			UserAvatar:   v.User.Avatar,
			CreateTime:   v.CreatedAt.Format("2006-01-02 15:04:05"),
			Likes:        int(v.Likes),
			ReplyTo:      v.ReplyTo,
			IsLiked:      likedMap[v.ID],
		}
	}

	return &requests.Pager{
		Pagination: requests.Pagination{Page: p.Page, Limit: p.Limit},
		Total:      total,
		Items:      data,
	}, nil
}

func (s *content) CreateComment(
	ctx context.Context,
	tenantID int64,
	userID int64,
	id int64,
	form *content_dto.CommentCreateForm,
) error {
	if userID == 0 {
		return errorx.ErrUnauthorized
	}
	uid := userID

	query := models.ContentQuery.WithContext(ctx).Where(models.ContentQuery.ID.Eq(id))
	if tenantID > 0 {
		query = query.Where(models.ContentQuery.TenantID.Eq(tenantID))
	}
	c, err := query.First()
	if err != nil {
		return errorx.ErrRecordNotFound
	}
	if err := s.ensureContentReadable(ctx, userID, c); err != nil {
		return err
	}

	comment := &models.Comment{
		TenantID:  c.TenantID,
		UserID:    uid,
		ContentID: id,
		Content:   form.Content,
		ReplyTo:   form.ReplyTo,
	}

	if err := models.CommentQuery.WithContext(ctx).Create(comment); err != nil {
		return errorx.ErrDatabaseError.WithCause(err)
	}
	return nil
}

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

	// Fetch comment for author
	query := models.CommentQuery.WithContext(ctx).Where(models.CommentQuery.ID.Eq(id))
	if tenantID > 0 {
		query = query.Where(models.CommentQuery.TenantID.Eq(tenantID))
	}
	cm, err := query.First()
	if err != nil {
		return errorx.ErrRecordNotFound
	}
	contentQuery := models.ContentQuery.WithContext(ctx).Where(models.ContentQuery.ID.Eq(cm.ContentID))
	if tenantID > 0 {
		contentQuery = contentQuery.Where(models.ContentQuery.TenantID.Eq(tenantID))
	}
	c, err := contentQuery.First()
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return errorx.ErrRecordNotFound
		}
		return errorx.ErrDatabaseError.WithCause(err)
	}
	if err := s.ensureContentReadable(ctx, userID, c); err != nil {
		return err
	}

	err = models.Q.Transaction(func(tx *models.Query) error {
		exists, _ := tx.UserCommentAction.WithContext(ctx).
			Where(tx.UserCommentAction.UserID.Eq(uid), tx.UserCommentAction.CommentID.Eq(id), tx.UserCommentAction.Type.Eq("like")).
			Exists()
		if exists {
			return nil
		}

		action := &models.UserCommentAction{UserID: uid, CommentID: id, Type: "like"}
		if err := tx.UserCommentAction.WithContext(ctx).Create(action); err != nil {
			return err
		}

		_, err := tx.Comment.WithContext(ctx).Where(tx.Comment.ID.Eq(id)).UpdateSimple(tx.Comment.Likes.Add(1))
		return err
	})
	if err != nil {
		return err
	}

	if Notification != nil {
		_ = Notification.Send(ctx, tenantID, cm.UserID, "interaction", "评论点赞", "有人点赞了您的评论")
	}
	return nil
}

func (s *content) GetLibrary(ctx context.Context, tenantID, userID int64) ([]user_dto.ContentItem, error) {
	if userID == 0 {
		return nil, errorx.ErrUnauthorized
	}
	uid := userID

	tbl, q := models.ContentAccessQuery.QueryContext(ctx)
	q = q.Where(tbl.UserID.Eq(uid), tbl.Status.Eq(consts.ContentAccessStatusActive))
	if tenantID > 0 {
		q = q.Where(tbl.TenantID.Eq(tenantID))
	}
	accessList, err := q.Find()
	if err != nil {
		return nil, errorx.ErrDatabaseError.WithCause(err)
	}

	if len(accessList) == 0 {
		return []user_dto.ContentItem{}, nil
	}

	var contentIDs []int64
	for _, a := range accessList {
		contentIDs = append(contentIDs, a.ContentID)
	}

	ctbl, cq := models.ContentQuery.QueryContext(ctx)
	var list []*models.Content
	cq = cq.Where(ctbl.ID.In(contentIDs...))
	if tenantID > 0 {
		cq = cq.Where(ctbl.TenantID.Eq(tenantID))
	}
	err = cq.
		UnderlyingDB().
		Preload("Author").
		Preload("ContentAssets.Asset").
		Find(&list).Error
	if err != nil {
		return nil, errorx.ErrDatabaseError.WithCause(err)
	}

	var data []user_dto.ContentItem
	for _, item := range list {
		dto := s.toContentItemDTO(item, 0, false)
		dto.IsPurchased = true
		data = append(data, dto)
	}
	return data, nil
}

func (s *content) GetFavorites(ctx context.Context, tenantID, userID int64) ([]user_dto.ContentItem, error) {
	return s.getInteractList(ctx, tenantID, userID, "favorite")
}

func (s *content) AddFavorite(ctx context.Context, tenantID, userID, contentId int64) error {
	return s.addInteract(ctx, tenantID, userID, contentId, "favorite")
}

func (s *content) RemoveFavorite(ctx context.Context, tenantID, userID, contentId int64) error {
	return s.removeInteract(ctx, tenantID, userID, contentId, "favorite")
}

func (s *content) GetLikes(ctx context.Context, tenantID, userID int64) ([]user_dto.ContentItem, error) {
	return s.getInteractList(ctx, tenantID, userID, "like")
}

func (s *content) AddLike(ctx context.Context, tenantID, userID, contentId int64) error {
	return s.addInteract(ctx, tenantID, userID, contentId, "like")
}

func (s *content) RemoveLike(ctx context.Context, tenantID, userID, contentId int64) error {
	return s.removeInteract(ctx, tenantID, userID, contentId, "like")
}

func (s *content) ListTopics(ctx context.Context, tenantID int64) ([]content_dto.Topic, error) {
	var results []struct {
		Genre string
		Count int
	}
	db := models.ContentQuery.WithContext(ctx).UnderlyingDB()
	if tenantID > 0 {
		db = db.Where("tenant_id = ?", tenantID)
	}
	err := db.
		Model(&models.Content{}).
		Where("status = ?", consts.ContentStatusPublished).
		Select("genre, count(*) as count").
		Group("genre").
		Scan(&results).Error
	if err != nil {
		return nil, errorx.ErrDatabaseError.WithCause(err)
	}

	if len(results) == 0 {
		return []content_dto.Topic{}, nil
	}

	genres := make([]string, 0, len(results))
	for _, r := range results {
		if r.Genre == "" {
			continue
		}
		genres = append(genres, r.Genre)
	}

	contentIDByGenre := make(map[string]int64, len(genres))
	contentMap := make(map[int64]*models.Content, len(genres))
	if len(genres) > 0 {
		// 批量获取每个分类最新内容，避免逐条查询导致 N+1。
		var rows []struct {
			Genre     string `gorm:"column:genre"`
			ContentID int64  `gorm:"column:content_id"`
		}
		contentQuery := models.ContentQuery.WithContext(ctx).UnderlyingDB().
			Model(&models.Content{}).
			Select("distinct on (genre) genre, id as content_id").
			Where("status = ?", consts.ContentStatusPublished).
			Where("genre IN ?", genres).
			Order("genre, published_at desc, id desc")
		if tenantID > 0 {
			contentQuery = contentQuery.Where("tenant_id = ?", tenantID)
		}
		if err := contentQuery.Scan(&rows).Error; err != nil {
			return nil, errorx.ErrDatabaseError.WithCause(err)
		}

		contentIDs := make([]int64, 0, len(rows))
		seen := make(map[int64]struct{}, len(rows))
		for _, row := range rows {
			if row.ContentID == 0 {
				continue
			}
			contentIDByGenre[row.Genre] = row.ContentID
			if _, ok := seen[row.ContentID]; ok {
				continue
			}
			seen[row.ContentID] = struct{}{}
			contentIDs = append(contentIDs, row.ContentID)
		}

		if len(contentIDs) > 0 {
			var contents []*models.Content
			if err := models.ContentQuery.WithContext(ctx).
				UnderlyingDB().
				Preload("ContentAssets").
				Preload("ContentAssets.Asset").
				Where("id IN ?", contentIDs).
				Find(&contents).Error; err != nil {
				return nil, errorx.ErrDatabaseError.WithCause(err)
			}
			for _, c := range contents {
				contentMap[c.ID] = c
			}
		}
	}

	topics := make([]content_dto.Topic, 0, len(results))
	for i, r := range results {
		if r.Genre == "" {
			continue
		}

		cover := ""
		if contentID, ok := contentIDByGenre[r.Genre]; ok {
			if c := contentMap[contentID]; c != nil {
				for _, ca := range c.ContentAssets {
					if (ca.Role == consts.ContentAssetRoleCover || ca.Role == consts.ContentAssetRoleMain) && ca.Asset != nil {
						cover = Common.GetAssetURL(ca.Asset.ObjectKey)
						if ca.Role == consts.ContentAssetRoleCover {
							break // Prefer cover
						}
					}
				}
			}
		}

		topics = append(topics, content_dto.Topic{
			ID:    int64(i + 1), // Use index as ID for aggregation results
			Title: r.Genre,
			Tag:   r.Genre,
			Count: r.Count,
			Cover: cover,
		})
	}
	return topics, nil
}

// Helpers

func (s *content) toContentItemDTO(item *models.Content, price float64, authorIsFollowing bool) content_dto.ContentItem {
	dto := content_dto.ContentItem{
		ID:                item.ID,
		TenantID:          item.TenantID,
		UserID:            item.UserID,
		Title:             item.Title,
		Genre:             item.Genre,
		Status:            string(item.Status),
		Visibility:        string(item.Visibility),
		AuthorID:          item.UserID,
		Views:             int(item.Views),
		Likes:             int(item.Likes),
		CreatedAt:         item.CreatedAt.Format("2006-01-02"),
		Price:             price,
		AuthorIsFollowing: authorIsFollowing,
	}
	if !item.PublishedAt.IsZero() {
		dto.PublishedAt = item.PublishedAt.Format("2006-01-02")
	}
	if item.Author != nil {
		dto.AuthorName = item.Author.Nickname
		dto.AuthorAvatar = item.Author.Avatar
	}

	// Determine Type and Cover from assets
	var hasVideo, hasAudio bool
	for _, asset := range item.ContentAssets {
		if asset.Asset == nil {
			continue
		}
		// Cover
		if asset.Role == consts.ContentAssetRoleCover {
			dto.Cover = Common.GetAssetURL(asset.Asset.ObjectKey)
		}
		// Type detection
		switch asset.Asset.Type {
		case consts.MediaAssetTypeVideo:
			hasVideo = true
		case consts.MediaAssetTypeAudio:
			hasAudio = true
		}
	}

	// Fallback for cover if not explicitly set as cover role (take first image or whatever)
	if dto.Cover == "" && len(item.ContentAssets) > 0 {
		for _, asset := range item.ContentAssets {
			if asset.Asset != nil && asset.Asset.Type == consts.MediaAssetTypeImage {
				dto.Cover = Common.GetAssetURL(asset.Asset.ObjectKey)
				break
			}
		}
	}

	if hasVideo {
		dto.Type = "video"
	} else if hasAudio {
		dto.Type = "audio"
	} else {
		dto.Type = "article"
	}

	return dto
}

func (s *content) ensureContentReadable(ctx context.Context, userID int64, item *models.Content) error {
	if item.Status != consts.ContentStatusPublished {
		// 未发布内容仅允许作者或租户管理员查看。
		return s.ensureContentOwnerOrAdmin(ctx, userID, item, "内容未发布")
	}

	switch item.Visibility {
	case consts.ContentVisibilityPublic, "":
		return nil
	case consts.ContentVisibilityPrivate:
		// 私密内容仅允许作者或租户管理员查看。
		return s.ensureContentOwnerOrAdmin(ctx, userID, item, "内容不可见")
	case consts.ContentVisibilityTenantOnly:
		// 店铺内可见内容需要登录并满足成员/购买条件。
		if userID == 0 {
			return errorx.ErrForbidden.WithMsg("请登录后查看该内容")
		}
		if item.UserID == userID {
			return nil
		}
		isAdmin, err := s.isTenantAdmin(ctx, item.TenantID, userID)
		if err != nil {
			return err
		}
		if isAdmin {
			return nil
		}
		// 已购买用户允许访问。
		hasAccess, err := models.ContentAccessQuery.WithContext(ctx).
			Where(models.ContentAccessQuery.UserID.Eq(userID),
				models.ContentAccessQuery.ContentID.Eq(item.ID),
				models.ContentAccessQuery.Status.Eq(consts.ContentAccessStatusActive)).
			Exists()
		if err != nil {
			return errorx.ErrDatabaseError.WithCause(err)
		}
		if hasAccess {
			return nil
		}
		isMember, err := s.isTenantMember(ctx, item.TenantID, userID)
		if err != nil {
			return err
		}
		if !isMember {
			return errorx.ErrForbidden.WithMsg("内容仅限本店铺成员访问")
		}
		return nil
	default:
		return nil
	}
}

func (s *content) ensureContentOwnerOrAdmin(ctx context.Context, userID int64, item *models.Content, msg string) error {
	if userID == 0 {
		return errorx.ErrForbidden.WithMsg(msg)
	}
	if item.UserID == userID {
		return nil
	}
	isAdmin, err := s.isTenantAdmin(ctx, item.TenantID, userID)
	if err != nil {
		return err
	}
	if !isAdmin {
		return errorx.ErrForbidden.WithMsg(msg)
	}
	return nil
}

func (s *content) isTenantAdmin(ctx context.Context, tenantID, userID int64) (bool, error) {
	if tenantID == 0 || userID == 0 {
		return false, nil
	}
	exists, err := models.TenantUserQuery.WithContext(ctx).
		Where(models.TenantUserQuery.TenantID.Eq(tenantID),
			models.TenantUserQuery.UserID.Eq(userID),
			models.TenantUserQuery.Role.Contains(types.Array[consts.TenantUserRole]{consts.TenantUserRoleTenantAdmin})).
		Exists()
	if err != nil {
		return false, errorx.ErrDatabaseError.WithCause(err)
	}
	return exists, nil
}

func (s *content) isTenantMember(ctx context.Context, tenantID, userID int64) (bool, error) {
	if tenantID == 0 || userID == 0 {
		return false, nil
	}
	tbl, q := models.TenantUserQuery.QueryContext(ctx)
	exists, err := q.Where(tbl.TenantID.Eq(tenantID), tbl.UserID.Eq(userID), tbl.Status.Eq(consts.UserStatusVerified)).Exists()
	if err != nil {
		return false, errorx.ErrDatabaseError.WithCause(err)
	}
	return exists, nil
}

func (s *content) toMediaURLs(assets []*models.ContentAsset) []content_dto.MediaURL {
	var urls []content_dto.MediaURL
	for _, ca := range assets {
		if ca.Asset != nil {
			url := Common.GetAssetURL(ca.Asset.ObjectKey)
			urls = append(urls, content_dto.MediaURL{
				Type: string(ca.Asset.Type),
				URL:  url,
			})
		}
	}
	return urls
}

func (s *content) addInteract(ctx context.Context, tenantID, userID, contentId int64, typ string) error {
	if userID == 0 {
		return errorx.ErrUnauthorized
	}
	uid := userID

	// Fetch content for author
	query := models.ContentQuery.WithContext(ctx).
		Where(models.ContentQuery.ID.Eq(contentId))
	if tenantID > 0 {
		query = query.Where(models.ContentQuery.TenantID.Eq(tenantID))
	}
	c, err := query.
		Select(models.ContentQuery.UserID, models.ContentQuery.Title, models.ContentQuery.Status, models.ContentQuery.TenantID).
		First()
	if err != nil {
		return errorx.ErrRecordNotFound
	}
	if err := s.ensureContentReadable(ctx, userID, c); err != nil {
		return err
	}

	err = models.Q.Transaction(func(tx *models.Query) error {
		exists, _ := tx.UserContentAction.WithContext(ctx).
			Where(tx.UserContentAction.UserID.Eq(uid), tx.UserContentAction.ContentID.Eq(contentId), tx.UserContentAction.Type.Eq(typ)).
			Exists()
		if exists {
			return nil
		}

		action := &models.UserContentAction{UserID: uid, ContentID: contentId, Type: typ}
		if err := tx.UserContentAction.WithContext(ctx).Create(action); err != nil {
			return err
		}

		if typ == "like" {
			contentQuery := tx.Content.WithContext(ctx).Where(tx.Content.ID.Eq(contentId))
			if tenantID > 0 {
				contentQuery = contentQuery.Where(tx.Content.TenantID.Eq(tenantID))
			}
			_, err := contentQuery.UpdateSimple(tx.Content.Likes.Add(1))
			return err
		}
		return nil
	})
	if err != nil {
		return err
	}

	if Notification != nil {
		actionName := "互动"
		switch typ {
		case "like":
			actionName = "点赞"
		case "favorite":
			actionName = "收藏"
		}
		_ = Notification.Send(ctx, tenantID, c.UserID, "interaction", "新的"+actionName, "有人"+actionName+"了您的作品: "+c.Title)
	}
	return nil
}

func (s *content) removeInteract(ctx context.Context, tenantID, userID, contentId int64, typ string) error {
	if userID == 0 {
		return errorx.ErrUnauthorized
	}
	uid := userID

	return models.Q.Transaction(func(tx *models.Query) error {
		res, err := tx.UserContentAction.WithContext(ctx).
			Where(tx.UserContentAction.UserID.Eq(uid), tx.UserContentAction.ContentID.Eq(contentId), tx.UserContentAction.Type.Eq(typ)).
			Delete()
		if err != nil {
			return err
		}
		if res.RowsAffected == 0 {
			return nil
		}

		if typ == "like" {
			contentQuery := tx.Content.WithContext(ctx).Where(tx.Content.ID.Eq(contentId))
			if tenantID > 0 {
				contentQuery = contentQuery.Where(tx.Content.TenantID.Eq(tenantID))
			}
			_, err := contentQuery.UpdateSimple(tx.Content.Likes.Sub(1))
			return err
		}
		return nil
	})
}

func (s *content) getInteractList(ctx context.Context, tenantID, userID int64, typ string) ([]user_dto.ContentItem, error) {
	if userID == 0 {
		return nil, errorx.ErrUnauthorized
	}
	uid := userID

	tbl, q := models.UserContentActionQuery.QueryContext(ctx)
	actions, err := q.Where(tbl.UserID.Eq(uid), tbl.Type.Eq(typ)).Find()
	if err != nil {
		return nil, errorx.ErrDatabaseError.WithCause(err)
	}

	if len(actions) == 0 {
		return []user_dto.ContentItem{}, nil
	}

	var contentIDs []int64
	for _, a := range actions {
		contentIDs = append(contentIDs, a.ContentID)
	}

	ctbl, cq := models.ContentQuery.QueryContext(ctx)
	var list []*models.Content
	cq = cq.Where(ctbl.ID.In(contentIDs...))
	if tenantID > 0 {
		cq = cq.Where(ctbl.TenantID.Eq(tenantID))
	}
	err = cq.
		UnderlyingDB().
		Preload("Author").
		Preload("ContentAssets.Asset").
		Find(&list).Error
	if err != nil {
		return nil, errorx.ErrDatabaseError.WithCause(err)
	}

	var data []user_dto.ContentItem
	for _, item := range list {
		data = append(data, s.toContentItemDTO(item, 0, false))
	}
	return data, nil
}
