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"

	"github.com/spf13/cast"
	"gorm.io/gorm"
)

// @provider
type content struct{}

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

	// Filters
	q = q.Where(tbl.Status.Eq(consts.ContentStatusPublished))
	if filter.Keyword != nil && *filter.Keyword != "" {
		keyword := "%" + *filter.Keyword + "%"
		q = q.Where(tbl.Title.Like(keyword)).Or(tbl.Description.Like(keyword))
	}
	if filter.Genre != nil && *filter.Genre != "" {
		q = q.Where(tbl.Genre.Eq(*filter.Genre))
	}
	if filter.TenantID != nil && *filter.TenantID != "" {
		tid := cast.ToInt64(*filter.TenantID)
		q = q.Where(tbl.TenantID.Eq(tid))
	}

	// 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.PublishedAt.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)
	}

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

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

func (s *content) Get(ctx context.Context, userID int64, id string) (*content_dto.ContentDetail, error) {
	cid := cast.ToInt64(id)

	// Increment Views
	_, _ = models.ContentQuery.WithContext(ctx).
		Where(models.ContentQuery.ID.Eq(cid)).
		UpdateSimple(models.ContentQuery.Views.Add(1))

	_, q := models.ContentQuery.QueryContext(ctx)

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

	// 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(cid),
				models.UserContentActionQuery.Type.Eq("like")).
			Exists()
		isFavorited, _ = models.UserContentActionQuery.WithContext(ctx).
			Where(models.UserContentActionQuery.UserID.Eq(uid),
				models.UserContentActionQuery.ContentID.Eq(cid),
				models.UserContentActionQuery.Type.Eq("favorite")).
			Exists()

		// Access Check
		if item.UserID == uid {
			hasAccess = true // Owner
		} else {
			// Check Purchase
			exists, _ := models.ContentAccessQuery.WithContext(ctx).
				Where(models.ContentAccessQuery.UserID.Eq(uid),
					models.ContentAccessQuery.ContentID.Eq(cid),
					models.ContentAccessQuery.Status.Eq(consts.ContentAccessStatusActive)).
				Exists()
			if exists {
				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)
			}
		}
	}

	detail := &content_dto.ContentDetail{
		ContentItem: s.toContentItemDTO(&item),
		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 // Update DTO field logic if needed. IsPurchased usually means "Bought". Owner implies access but not necessarily purchased. But for UI "Play" button, IsPurchased=true is fine.

	return detail, nil
}

func (s *content) ListComments(ctx context.Context, userID int64, id string, page int) (*requests.Pager, error) {
	cid := cast.ToInt64(id)
	tbl, q := models.CommentQuery.QueryContext(ctx)

	q = q.Where(tbl.ContentID.Eq(cid)).Preload(tbl.User)
	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:           cast.ToString(v.ID),
			Content:      v.Content,
			UserID:       cast.ToString(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:      cast.ToString(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,
	userID int64,
	id string,
	form *content_dto.CommentCreateForm,
) error {
	if userID == 0 {
		return errorx.ErrUnauthorized
	}
	uid := userID
	cid := cast.ToInt64(id)

	c, err := models.ContentQuery.WithContext(ctx).Where(models.ContentQuery.ID.Eq(cid)).First()
	if err != nil {
		return errorx.ErrRecordNotFound
	}

	comment := &models.Comment{
		TenantID:  c.TenantID,
		UserID:    uid,
		ContentID: cid,
		Content:   form.Content,
		ReplyTo:   cast.ToInt64(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, userID int64, id string) error {
	if userID == 0 {
		return errorx.ErrUnauthorized
	}
	uid := userID
	cmid := cast.ToInt64(id)

	// Fetch comment for author
	cm, err := models.CommentQuery.WithContext(ctx).Where(models.CommentQuery.ID.Eq(cmid)).First()
	if err != nil {
		return errorx.ErrRecordNotFound
	}

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

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

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

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

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

	tbl, q := models.ContentAccessQuery.QueryContext(ctx)
	accessList, err := q.Where(tbl.UserID.Eq(uid), tbl.Status.Eq(consts.ContentAccessStatusActive)).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
	err = cq.Where(ctbl.ID.In(contentIDs...)).
		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)
		dto.IsPurchased = true
		data = append(data, dto)
	}
	return data, nil
}

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

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

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

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

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

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

func (s *content) ListTopics(ctx context.Context) ([]content_dto.Topic, error) {
	var results []struct {
		Genre string
		Count int
	}
	err := models.ContentQuery.WithContext(ctx).UnderlyingDB().
		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)
	}

	var topics []content_dto.Topic
	for i, r := range results {
		if r.Genre == "" {
			continue
		}

		// Fetch latest content in this genre to get a cover
		var c models.Content
		models.ContentQuery.WithContext(ctx).
			Where(models.ContentQuery.Genre.Eq(r.Genre), models.ContentQuery.Status.Eq(consts.ContentStatusPublished)).
			Order(models.ContentQuery.PublishedAt.Desc()).
			UnderlyingDB().
			Preload("ContentAssets").
			Preload("ContentAssets.Asset").
			First(&c)

		cover := ""
		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:    cast.ToString(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) content_dto.ContentItem {
	dto := content_dto.ContentItem{
		ID:       cast.ToString(item.ID),
		Title:    item.Title,
		Genre:    item.Genre,
		AuthorID: cast.ToString(item.UserID),
		Views:    int(item.Views),
		Likes:    int(item.Likes),
	}
	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) 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, userID int64, contentId, typ string) error {
	if userID == 0 {
		return errorx.ErrUnauthorized
	}
	uid := userID
	cid := cast.ToInt64(contentId)

	// Fetch content for author
	c, err := models.ContentQuery.WithContext(ctx).
		Where(models.ContentQuery.ID.Eq(cid)).
		Select(models.ContentQuery.UserID, models.ContentQuery.Title).
		First()
	if err != nil {
		return errorx.ErrRecordNotFound
	}

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

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

		if typ == "like" {
			_, err := tx.Content.WithContext(ctx).Where(tx.Content.ID.Eq(cid)).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, c.UserID, "interaction", "新的"+actionName, "有人"+actionName+"了您的作品: "+c.Title)
	}
	return nil
}

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

	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(cid), tx.UserContentAction.Type.Eq(typ)).
			Delete()
		if err != nil {
			return err
		}
		if res.RowsAffected == 0 {
			return nil
		}

		if typ == "like" {
			_, err := tx.Content.WithContext(ctx).Where(tx.Content.ID.Eq(cid)).UpdateSimple(tx.Content.Likes.Sub(1))
			return err
		}
		return nil
	})
}

func (s *content) getInteractList(ctx context.Context, 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
	err = cq.Where(ctbl.ID.In(contentIDs...)).
		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))
	}
	return data, nil
}
