/*
*
* Reply Resources File
* Copyright Azareal 2016 - 2020
*
 */
package common

import (
	"database/sql"
	"errors"
	"html"
	"strconv"
	"time"

	qgen "github.com/Azareal/Gosora/query_gen"
)

type ReplyUser struct {
	Reply

	ContentHtml   string
	UserLink      string
	CreatedByName string
	Avatar        string
	MicroAvatar   string
	ClassName     string
	Tag           string
	URL           string
	//URLPrefix string
	//URLName   string
	Group      int
	Level      int
	ActionIcon string

	Attachments []*MiniAttachment
	Deletable   bool
}

type Reply struct {
	ID        int
	ParentID  int
	Content   string
	CreatedBy int
	//Group        int
	CreatedAt    time.Time
	LastEdit     int
	LastEditBy   int
	ContentLines int
	IP           string
	Liked        bool
	LikeCount    int
	AttachCount  uint16
	ActionType   string
}

var ErrAlreadyLiked = errors.New("You already liked this!")
var replyStmts ReplyStmts

type ReplyStmts struct {
	isLiked                *sql.Stmt
	createLike             *sql.Stmt
	edit                   *sql.Stmt
	setPoll                *sql.Stmt
	delete                 *sql.Stmt
	addLikesToReply        *sql.Stmt
	removeRepliesFromTopic *sql.Stmt
	deleteLikesForReply    *sql.Stmt
	deleteActivity         *sql.Stmt
	deleteActivitySubs     *sql.Stmt

	updateTopicReplies  *sql.Stmt
	updateTopicReplies2 *sql.Stmt

	getAidsOfReply *sql.Stmt
}

func init() {
	DbInits.Add(func(acc *qgen.Accumulator) error {
		re := "replies"
		replyStmts = ReplyStmts{
			isLiked:                acc.Select("likes").Columns("targetItem").Where("sentBy=? and targetItem=? and targetType='replies'").Prepare(),
			createLike:             acc.Insert("likes").Columns("weight,targetItem,targetType,sentBy,createdAt").Fields("?,?,?,?,UTC_TIMESTAMP()").Prepare(),
			edit:                   acc.Update(re).Set("content=?,parsed_content=?").Where("rid=? AND poll=0").Prepare(),
			setPoll:                acc.Update(re).Set("poll=?").Where("rid=? AND poll=0").Prepare(),
			delete:                 acc.Delete(re).Where("rid=?").Prepare(),
			addLikesToReply:        acc.Update(re).Set("likeCount=likeCount+?").Where("rid=?").Prepare(),
			removeRepliesFromTopic: acc.Update("topics").Set("postCount=postCount-?").Where("tid=?").Prepare(),
			deleteLikesForReply:    acc.Delete("likes").Where("targetItem=? AND targetType='replies'").Prepare(),
			deleteActivity:         acc.Delete("activity_stream").Where("elementID=? AND elementType='post'").Prepare(),
			deleteActivitySubs:     acc.Delete("activity_subscriptions").Where("targetID=? AND targetType='post'").Prepare(),

			// TODO: Optimise this to avoid firing an update if it's not the last reply in a topic. We will need to set lastReplyID properly in other places and in the patcher first so we can use it here.
			updateTopicReplies:  acc.RawPrepare("UPDATE topics t INNER JOIN replies r ON t.tid=r.tid SET t.lastReplyBy=r.createdBy, t.lastReplyAt=r.createdAt, t.lastReplyID=r.rid WHERE t.tid=?"),
			updateTopicReplies2: acc.Update("topics").Set("lastReplyAt=createdAt,lastReplyBy=createdBy,lastReplyID=0").Where("postCount=1 AND tid=?").Prepare(),

			getAidsOfReply: acc.Select("attachments").Columns("attachID").Where("originID=? AND originTable='replies'").Prepare(),
		}
		return acc.FirstError()
	})
}

// TODO: Write tests for this
// TODO: Wrap these queries in a transaction to make sure the state is consistent
func (r *Reply) Like(uid int) (err error) {
	var rid int // unused, just here to avoid mutating reply.ID
	err = replyStmts.isLiked.QueryRow(uid, r.ID).Scan(&rid)
	if err != nil && err != ErrNoRows {
		return err
	} else if err != ErrNoRows {
		return ErrAlreadyLiked
	}

	score := 1
	_, err = replyStmts.createLike.Exec(score, r.ID, "replies", uid)
	if err != nil {
		return err
	}
	_, err = replyStmts.addLikesToReply.Exec(1, r.ID)
	if err != nil {
		return err
	}
	_, err = userStmts.incLiked.Exec(1, uid)
	_ = Rstore.GetCache().Remove(r.ID)
	return err
}

// TODO: Use a transaction
func (r *Reply) Unlike(uid int) error {
	err := Likes.Delete(r.ID, "replies")
	if err != nil {
		return err
	}
	_, err = replyStmts.addLikesToReply.Exec(-1, r.ID)
	if err != nil {
		return err
	}
	_, err = userStmts.decLiked.Exec(1, uid)
	_ = Rstore.GetCache().Remove(r.ID)
	return err
}

// TODO: Refresh topic list?
func (r *Reply) Delete() error {
	creator, err := Users.Get(r.CreatedBy)
	if err == nil {
		err = creator.DecreasePostStats(WordCount(r.Content), false)
		if err != nil {
			return err
		}
	} else if err != ErrNoRows {
		return err
	}

	_, err = replyStmts.delete.Exec(r.ID)
	if err != nil {
		return err
	}
	// TODO: Move this bit to *Topic
	_, err = replyStmts.removeRepliesFromTopic.Exec(1, r.ParentID)
	if err != nil {
		return err
	}
	_, err = replyStmts.updateTopicReplies.Exec(r.ParentID)
	if err != nil {
		return err
	}
	_, err = replyStmts.updateTopicReplies2.Exec(r.ParentID)
	tc := Topics.GetCache()
	if tc != nil {
		tc.Remove(r.ParentID)
	}
	_ = Rstore.GetCache().Remove(r.ID)
	if err != nil {
		return err
	}
	_, err = replyStmts.deleteLikesForReply.Exec(r.ID)
	if err != nil {
		return err
	}
	err = handleReplyAttachments(r.ID)
	if err != nil {
		return err
	}
	err = Activity.DeleteByParamsExtra("reply", r.ParentID, "topic", strconv.Itoa(r.ID))
	if err != nil {
		return err
	}
	_, err = replyStmts.deleteActivitySubs.Exec(r.ID)
	if err != nil {
		return err
	}
	_, err = replyStmts.deleteActivity.Exec(r.ID)
	return err
}

func (r *Reply) SetPost(content string) error {
	topic, err := r.Topic()
	if err != nil {
		return err
	}
	content = PreparseMessage(html.UnescapeString(content))
	parsedContent := ParseMessage(content, topic.ParentID, "forums", nil, nil)
	_, err = replyStmts.edit.Exec(content, parsedContent, r.ID) // TODO: Sniff if this changed anything to see if we hit an existing poll
	_ = Rstore.GetCache().Remove(r.ID)
	return err
}

// TODO: Write tests for this
func (r *Reply) SetPoll(pollID int) error {
	_, err := replyStmts.setPoll.Exec(pollID, r.ID) // TODO: Sniff if this changed anything to see if we hit a poll
	_ = Rstore.GetCache().Remove(r.ID)
	return err
}

func (r *Reply) Topic() (*Topic, error) {
	return Topics.Get(r.ParentID)
}

func (r *Reply) GetID() int {
	return r.ID
}

func (r *Reply) GetTable() string {
	return "replies"
}

// Copy gives you a non-pointer concurrency safe copy of the reply
func (r *Reply) Copy() Reply {
	return *r
}