package service

import (
	"bbs-go/model/constants"
	"bbs-go/util/config"
	"bbs-go/util/es"
	"bbs-go/util/event"
	"bbs-go/util/urls"
	"errors"
	"math"
	"net/http"
	"path"
	"time"

	"bbs-go/util/simple/date"
	"bbs-go/util/simple/json"

	"bbs-go/util/simple"

	"github.com/gorilla/feeds"
	"github.com/sirupsen/logrus"
	"gorm.io/gorm"

	"bbs-go/cache"
	"bbs-go/dao"
	"bbs-go/model"
	"bbs-go/util/common"
)

var TopicService = newTopicService()

func newTopicService() *topicService {
	return &topicService{}
}

type topicService struct{}

func (s *topicService) Get(id int64) *model.Topic {
	return dao.TopicDao.Get(simple.DB(), id)
}

func (s *topicService) Take(where ...interface{}) *model.Topic {
	return dao.TopicDao.Take(simple.DB(), where...)
}

func (s *topicService) Find(cnd *simple.SqlCnd) []model.Topic {
	return dao.TopicDao.Find(simple.DB(), cnd)
}

func (s *topicService) FindOne(cnd *simple.SqlCnd) *model.Topic {
	return dao.TopicDao.FindOne(simple.DB(), cnd)
}

func (s *topicService) FindPageByParams(params *simple.QueryParams) (list []model.Topic, paging *simple.Paging) {
	return dao.TopicDao.FindPageByParams(simple.DB(), params)
}

func (s *topicService) FindPageByCnd(cnd *simple.SqlCnd) (list []model.Topic, paging *simple.Paging) {
	return dao.TopicDao.FindPageByCnd(simple.DB(), cnd)
}

func (s *topicService) Count(cnd *simple.SqlCnd) int64 {
	return dao.TopicDao.Count(simple.DB(), cnd)
}

func (s *topicService) Updates(id int64, columns map[string]interface{}) error {
	if err := dao.TopicDao.Updates(simple.DB(), id, columns); err != nil {
		return err
	}

	// 添加索引
	es.UpdateTopicIndex(s.Get(id))

	return nil
}

func (s *topicService) UpdateColumn(id int64, name string, value interface{}) error {
	if err := dao.TopicDao.UpdateColumn(simple.DB(), id, name, value); err != nil {
		return err
	}

	// 添加索引
	es.UpdateTopicIndex(s.Get(id))

	return nil
}

// Delete 删除
func (s *topicService) Delete(topicId, deleteUserId int64, r *http.Request) error {
	topic := s.Get(topicId)
	if topic == nil {
		return nil
	}
	err := dao.TopicDao.UpdateColumn(simple.DB(), topicId, "status", constants.StatusDeleted)
	if err == nil {
		// 添加索引
		es.UpdateTopicIndex(s.Get(topicId))
		// 删掉标签文章
		TopicTagService.DeleteByTopicId(topicId)
		// 发送消息
		MessageService.SendTopicDeleteMsg(topicId, deleteUserId)
		// 发送事件
		event.Send(event.TopicDeleteEvent{
			UserId:       topic.UserId,
			TopicId:      topic.Id,
			DeleteUserId: deleteUserId,
		})
	}
	return err
}

// Undelete 取消删除
func (s *topicService) Undelete(id int64) error {
	err := dao.TopicDao.UpdateColumn(simple.DB(), id, "status", constants.StatusOk)
	if err == nil {
		// 删掉标签文章
		TopicTagService.UndeleteByTopicId(id)
		// 添加索引
		es.UpdateTopicIndex(s.Get(id))
	}
	return err
}

// Publish 发表
func (s *topicService) Publish(userId int64, form model.CreateTopicForm) (*model.Topic, *simple.CodeError) {
	if form.Type == constants.TopicTypeTweet {
		if simple.IsBlank(form.Content) && len(form.ImageList) == 0 {
			return nil, simple.NewErrorMsg("内容或图片不能为空")
		}
	} else {
		if simple.IsBlank(form.Title) {
			return nil, simple.NewErrorMsg("标题不能为空")
		}

		if simple.IsBlank(form.Content) {
			return nil, simple.NewErrorMsg("内容不能为空")
		}

		if simple.RuneLen(form.Title) > 128 {
			return nil, simple.NewErrorMsg("标题长度不能超过128")
		}
	}

	if form.NodeId <= 0 {
		form.NodeId = SysConfigService.GetConfig().DefaultNodeId
		if form.NodeId <= 0 {
			return nil, simple.NewErrorMsg("请选择节点")
		}
	}
	node := dao.TopicNodeDao.Get(simple.DB(), form.NodeId)
	if node == nil || node.Status != constants.StatusOk {
		return nil, simple.NewErrorMsg("节点不存在")
	}

	now := date.NowTimestamp()
	topic := &model.Topic{
		Type:            form.Type,
		UserId:          userId,
		NodeId:          form.NodeId,
		Title:           form.Title,
		Content:         form.Content,
		Status:          constants.StatusOk,
		LastCommentTime: now,
		CreateTime:      now,
	}

	if len(form.ImageList) > 0 {
		imageListStr, err := json.ToStr(form.ImageList)
		if err == nil {
			topic.ImageList = imageListStr
		} else {
			logrus.Error(err)
		}
	}

	err := simple.DB().Transaction(func(tx *gorm.DB) error {
		tagIds := dao.TagDao.GetOrCreates(tx, form.Tags)
		err := dao.TopicDao.Create(tx, topic)
		if err != nil {
			return err
		}
		dao.TopicTagDao.AddTopicTags(tx, topic.Id, tagIds)
		return nil
	})
	if err == nil {
		// 添加索引
		es.UpdateTopicIndex(topic)
		// 用户话题计数
		UserService.IncrTopicCount(userId)
		// 获得积分
		UserService.IncrScoreForPostTopic(topic)
		// 发送事件
		event.Send(event.TopicCreateEvent{
			UserId:     topic.UserId,
			TopicId:    topic.Id,
			CreateTime: topic.CreateTime,
		})
	}
	return topic, simple.FromError(err)
}

// 更新
func (s *topicService) Edit(topicId, nodeId int64, tags []string, title, content string) *simple.CodeError {
	if len(title) == 0 {
		return simple.NewErrorMsg("标题不能为空")
	}

	if simple.RuneLen(title) > 128 {
		return simple.NewErrorMsg("标题长度不能超过128")
	}

	node := dao.TopicNodeDao.Get(simple.DB(), nodeId)
	if node == nil || node.Status != constants.StatusOk {
		return simple.NewErrorMsg("节点不存在")
	}

	err := simple.DB().Transaction(func(tx *gorm.DB) error {
		err := dao.TopicDao.Updates(simple.DB(), topicId, map[string]interface{}{
			"node_id": nodeId,
			"title":   title,
			"content": content,
		})
		if err != nil {
			return err
		}

		tagIds := dao.TagDao.GetOrCreates(tx, tags)       // 创建帖子对应标签
		dao.TopicTagDao.DeleteTopicTags(tx, topicId)      // 先删掉所有的标签
		dao.TopicTagDao.AddTopicTags(tx, topicId, tagIds) // 然后重新添加标签
		return nil
	})

	// 添加索引
	es.UpdateTopicIndex(s.Get(topicId))

	return simple.FromError(err)
}

// 推荐
func (s *topicService) SetRecommend(topicId int64, recommend bool) error {
	topic := s.Get(topicId)
	if topic == nil || topic.Status != constants.StatusOk {
		return errors.New("帖子不存在")
	}
	if topic.Recommend == recommend { // 推荐状态没变更
		return nil
	}
	if recommend {
		if err := s.Updates(topicId, map[string]interface{}{
			"recommend":      recommend,
			"recommend_time": date.NowTimestamp(),
		}); err != nil {
			return err
		}
		MessageService.SendTopicRecommendMsg(topicId)
	} else {
		if err := s.UpdateColumn(topicId, "recommend", recommend); err != nil {
			return err
		}
	}

	// 添加索引
	es.UpdateTopicIndex(s.Get(topicId))

	return nil
}

// 话题的标签
func (s *topicService) GetTopicTags(topicId int64) []model.Tag {
	topicTags := dao.TopicTagDao.Find(simple.DB(), simple.NewSqlCnd().Where("topic_id = ?", topicId))

	var tagIds []int64
	for _, topicTag := range topicTags {
		tagIds = append(tagIds, topicTag.TagId)
	}
	return cache.TagCache.GetList(tagIds)
}

// 获取帖子分页列表
func (s *topicService) GetTopics(nodeId, cursor int64, recommend bool) (topics []model.Topic, nextCursor int64, hasMore bool) {
	limit := 20
	cnd := simple.NewSqlCnd()
	if nodeId > 0 {
		cnd.Eq("node_id", nodeId)
	}
	if cursor > 0 {
		cnd.Lt("last_comment_time", cursor)
	}
	if recommend {
		cnd.Eq("recommend", true)
	}
	cnd.Eq("status", constants.StatusOk).Desc("last_comment_time").Limit(limit)
	topics = dao.TopicDao.Find(simple.DB(), cnd)
	if len(topics) > 0 {
		nextCursor = topics[len(topics)-1].LastCommentTime
		hasMore = len(topics) >= limit
	} else {
		nextCursor = cursor
	}
	return
}

// 指定标签下话题列表
func (s *topicService) GetTagTopics(tagId, cursor int64) (topics []model.Topic, nextCursor int64, hasMore bool) {
	limit := 20
	topicTags := dao.TopicTagDao.Find(simple.DB(), simple.NewSqlCnd().
		Eq("tag_id", tagId).
		Eq("status", constants.StatusOk).
		Desc("last_comment_time").Limit(limit))
	if len(topicTags) > 0 {
		nextCursor = topicTags[len(topicTags)-1].LastCommentTime

		var topicIds []int64
		for _, topicTag := range topicTags {
			topicIds = append(topicIds, topicTag.TopicId)
		}

		topicsMap := s.GetTopicInIds(topicIds)
		if topicsMap != nil {
			for _, topicTag := range topicTags {
				if topic, found := topicsMap[topicTag.TopicId]; found {
					topics = append(topics, topic)
				}
			}
		}
	} else {
		nextCursor = cursor
	}
	hasMore = len(topicTags) >= limit
	return
}

func (s *topicService) GetTopicByIds(topicIds []int64) (topics []model.Topic) {
	topicsMap := s.GetTopicInIds(topicIds)
	for _, topicId := range topicIds {
		topic, found := topicsMap[topicId]
		if found {
			topics = append(topics, topic)
		}
	}
	return
}

// GetTopicInIds 根据编号批量获取主题
func (s *topicService) GetTopicInIds(topicIds []int64) map[int64]model.Topic {
	if len(topicIds) == 0 {
		return nil
	}
	var topics []model.Topic
	simple.DB().Where("id in (?)", topicIds).Find(&topics)

	topicsMap := make(map[int64]model.Topic, len(topics))
	for _, topic := range topics {
		topicsMap[topic.Id] = topic
	}
	return topicsMap
}

// 浏览数+1
func (s *topicService) IncrViewCount(topicId int64) {
	simple.DB().Exec("update t_topic set view_count = view_count + 1 where id = ?", topicId)
}

// 当帖子被评论的时候，更新最后回复时间、回复数量+1
func (s *topicService) OnComment(topicId int64, comment *model.Comment) {
	_ = simple.DB().Transaction(func(tx *gorm.DB) error {
		if err := dao.TopicDao.Updates(tx, topicId, map[string]interface{}{
			"last_comment_time":    comment.CreateTime,
			"last_comment_user_id": comment.UserId,
			"comment_count":        gorm.Expr("comment_count + 1"),
		}); err != nil {
			return err
		}
		if err := tx.Exec("update t_topic_tag set last_comment_time = ?, last_comment_user_id = ? where topic_id = ?",
			comment.CreateTime, comment.UserId, topicId).Error; err != nil {
			return err
		}
		return nil
	})
}

// rss
func (s *topicService) GenerateRss() {
	topics := dao.TopicDao.Find(simple.DB(),
		simple.NewSqlCnd().Where("status = ?", constants.StatusOk).Desc("id").Limit(200))

	var items []*feeds.Item
	for _, topic := range topics {
		topicUrl := urls.TopicUrl(topic.Id)
		user := cache.UserCache.Get(topic.UserId)
		if user == nil {
			continue
		}
		item := &feeds.Item{
			Title:       topic.Title,
			Link:        &feeds.Link{Href: topicUrl},
			Description: common.GetMarkdownSummary(topic.Content),
			Author:      &feeds.Author{Name: user.Avatar, Email: user.Email.String},
			Created:     date.FromTimestamp(topic.CreateTime),
		}
		items = append(items, item)
	}
	siteTitle := cache.SysConfigCache.GetValue(constants.SysConfigSiteTitle)
	siteDescription := cache.SysConfigCache.GetValue(constants.SysConfigSiteDescription)
	feed := &feeds.Feed{
		Title:       siteTitle,
		Link:        &feeds.Link{Href: config.Instance.BaseUrl},
		Description: siteDescription,
		Author:      &feeds.Author{Name: siteTitle},
		Created:     time.Now(),
		Items:       items,
	}
	atom, err := feed.ToAtom()
	if err != nil {
		logrus.Error(err)
	} else {
		_ = simple.WriteString(path.Join(config.Instance.StaticPath, "topic_atom.xml"), atom, false)
	}

	rss, err := feed.ToRss()
	if err != nil {
		logrus.Error(err)
	} else {
		_ = simple.WriteString(path.Join(config.Instance.StaticPath, "topic_rss.xml"), rss, false)
	}
}

func (s *topicService) ScanByUser(userId int64, callback func(topics []model.Topic)) {
	var cursor int64 = 0
	for {
		list := dao.TopicDao.Find(simple.DB(), simple.NewSqlCnd().
			Eq("user_id", userId).Gt("id", cursor).Asc("id").Limit(1000))
		if len(list) == 0 {
			break
		}
		cursor = list[len(list)-1].Id
		callback(list)
	}
}

func (s *topicService) Scan(callback func(topics []model.Topic)) {
	var cursor int64 = 0
	for {
		list := dao.TopicDao.Find(simple.DB(), simple.NewSqlCnd().
			Gt("id", cursor).Asc("id").Limit(1000))
		if len(list) == 0 {
			break
		}
		cursor = list[len(list)-1].Id
		callback(list)
	}
}

// 倒序扫描
func (s *topicService) ScanDesc(callback func(topics []model.Topic)) {
	var cursor int64 = math.MaxInt64
	for {
		list := dao.TopicDao.Find(simple.DB(), simple.NewSqlCnd().
			Cols("id", "status", "create_time").
			Lt("id", cursor).Desc("id").Limit(1000))
		if len(list) == 0 {
			break
		}
		cursor = list[len(list)-1].Id
		callback(list)
	}
}

// 倒序扫描
func (s *topicService) ScanDescWithDate(dateFrom, dateTo int64, callback func(topics []model.Topic)) {
	var cursor int64 = math.MaxInt64
	for {
		list := dao.TopicDao.Find(simple.DB(), simple.NewSqlCnd().
			Cols("id", "status", "create_time", "update_time").
			Lt("id", cursor).Gte("create_time", dateFrom).Lt("create_time", dateTo).Desc("id").Limit(1000))
		if len(list) == 0 {
			break
		}
		cursor = list[len(list)-1].Id
		callback(list)
	}
}

func (s *topicService) GetUserTopics(userId, cursor int64) (topics []model.Topic, nextCursor int64, hasMore bool) {
	limit := 20
	cnd := simple.NewSqlCnd()
	if userId > 0 {
		cnd.Eq("user_id", userId)
	}
	if cursor > 0 {
		cnd.Lt("id", cursor)
	}
	cnd.Eq("status", constants.StatusOk).Desc("id").Limit(limit)
	topics = dao.TopicDao.Find(simple.DB(), cnd)
	if len(topics) > 0 {
		nextCursor = topics[len(topics)-1].Id
		hasMore = len(topics) >= limit
	} else {
		nextCursor = cursor
	}
	return
}
