// Copyright 2022 ROC. All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.

package web

import (
	"context"
	"fmt"

	"time"
	"unicode/utf8"

	"github.com/alimy/mir/v3"
	"github.com/gin-gonic/gin"
	api "github.com/rocboss/paopao-ce/auto/api/v1"
	"github.com/rocboss/paopao-ce/internal/core"
	"github.com/rocboss/paopao-ce/internal/model/web"
	"github.com/rocboss/paopao-ce/internal/servants/base"
	"github.com/rocboss/paopao-ce/internal/servants/chain"
	"github.com/rocboss/paopao-ce/pkg/convert"
	"github.com/rocboss/paopao-ce/pkg/xerror"
	"github.com/sirupsen/logrus"
)

const (
	// _MaxWhisperNumDaily 当日单用户私信总数限制(TODO 配置化、积分兑换等)
	_MaxWhisperNumDaily = 20
	_MaxCaptchaTimes    = 2
)

var (
	_ api.Core        = (*coreSrv)(nil)
	_ api.CoreBinding = (*coreBinding)(nil)
	_ api.CoreRender  = (*coreRender)(nil)
)

type coreSrv struct {
	api.UnimplementedCoreServant
	*base.DaoServant

	oss core.ObjectStorageService
}

type coreBinding struct {
	*api.UnimplementedCoreBinding
}

type coreRender struct {
	*api.UnimplementedCoreRender
}

func (b *coreBinding) BindGetUserInfo(c *gin.Context) (*web.UserInfoReq, mir.Error) {
	username, exist := base.UserNameFrom(c)
	if !exist {
		return nil, xerror.UnauthorizedAuthNotExist
	}
	return &web.UserInfoReq{
		Username: username,
	}, nil
}

func (b *coreBinding) BindGetMessages(c *gin.Context) (*web.GetMessagesReq, mir.Error) {
	v, err := web.BasePageReqFrom(c)
	return (*web.GetMessagesReq)(v), err
}

func (b *coreBinding) BindGetCollections(c *gin.Context) (*web.GetCollectionsReq, mir.Error) {
	v, err := web.BasePageReqFrom(c)
	return (*web.GetCollectionsReq)(v), err
}

func (b *coreBinding) BindGetStars(c *gin.Context) (*web.GetStarsReq, mir.Error) {
	v, err := web.BasePageReqFrom(c)
	return (*web.GetStarsReq)(v), err
}

func (b *coreBinding) BindSuggestTags(c *gin.Context) (*web.SuggestTagsReq, mir.Error) {
	return &web.SuggestTagsReq{
		Keyword: c.Query("k"),
	}, nil
}

func (b *coreBinding) BindSuggestUsers(c *gin.Context) (*web.SuggestUsersReq, mir.Error) {
	return &web.SuggestUsersReq{
		Keyword: c.Query("k"),
	}, nil
}

func (b *coreBinding) BindTweetCollectionStatus(c *gin.Context) (*web.TweetCollectionStatusReq, mir.Error) {
	UserId, exist := base.UserIdFrom(c)
	if !exist {
		return nil, xerror.UnauthorizedAuthNotExist
	}
	return &web.TweetCollectionStatusReq{
		SimpleInfo: web.SimpleInfo{
			Uid: UserId,
		},
		TweetId: convert.StrTo(c.Query("id")).MustInt64(),
	}, nil
}

func (b *coreBinding) BindTweetStarStatus(c *gin.Context) (*web.TweetStarStatusReq, mir.Error) {
	UserId, exist := base.UserIdFrom(c)
	if !exist {
		return nil, xerror.UnauthorizedAuthNotExist
	}
	return &web.TweetStarStatusReq{
		SimpleInfo: web.SimpleInfo{
			Uid: UserId,
		},
		TweetId: convert.StrTo(c.Query("id")).MustInt64(),
	}, nil
}

func (s *coreSrv) Chain() gin.HandlersChain {
	return gin.HandlersChain{chain.JWT()}
}

func (s *coreSrv) SyncSearchIndex(req *web.SyncSearchIndexReq) mir.Error {
	if req.User != nil && req.User.IsAdmin {
		go s.PushPostsToSearch(context.Background())
	}
	return nil
}

func (s *coreSrv) GetUserInfo(req *web.UserInfoReq) (*web.UserInfoResp, mir.Error) {
	user, err := s.Ds.GetUserByUsername(req.Username)
	if err != nil {
		return nil, xerror.UnauthorizedAuthNotExist
	}
	if user.Model == nil || user.ID < 0 {
		return nil, xerror.UnauthorizedAuthNotExist
	}
	resp := &web.UserInfoResp{
		Id:       user.ID,
		Nickname: user.Nickname,
		Username: user.Username,
		Status:   user.Status,
		Avatar:   user.Avatar,
		Balance:  user.Balance,
		IsAdmin:  user.IsAdmin,
	}
	if user.Phone != "" && len(user.Phone) == 11 {
		resp.Phone = user.Phone[0:3] + "****" + user.Phone[7:]
	}
	return resp, nil
}

func (s *coreSrv) GetUnreadMsgCount(req *web.GetUnreadMsgCountReq) (*web.GetUnreadMsgCountResp, mir.Error) {
	count, err := s.Ds.GetUnreadCount(req.Uid)
	if err != nil {
		return nil, xerror.ServerError
	}
	return &web.GetUnreadMsgCountResp{
		Count: count,
	}, nil
}

func (s *coreSrv) GetMessages(req *web.GetMessagesReq) (*web.GetMessagesResp, mir.Error) {
	conditions := &core.ConditionsT{
		"receiver_user_id": req.UserId,
		"ORDER":            "id DESC",
	}
	messages, err := s.Ds.GetMessages(conditions, (req.Page-1)*req.PageSize, req.PageSize)
	for _, mf := range messages {
		if mf.SenderUserID > 0 {
			user, err := s.Ds.GetUserByID(mf.SenderUserID)
			if err == nil {
				mf.SenderUser = user.Format()
			}
		}
		// 好友申请消息不需要获取其他信息
		if mf.Type == core.MsgTypeRequestingFriend {
			continue
		}
		if mf.PostID > 0 {
			post, err := s.GetTweetBy(mf.PostID)
			if err == nil {
				mf.Post = post
				if mf.CommentID > 0 {
					comment, err := s.Ds.GetCommentByID(mf.CommentID)
					if err == nil {
						mf.Comment = comment
						if mf.ReplyID > 0 {
							reply, err := s.Ds.GetCommentReplyByID(mf.ReplyID)
							if err == nil {
								mf.Reply = reply
							}
						}
					}
				}
			}
		}
	}
	if err != nil {
		logrus.Errorf("Ds.GetMessages err: %v\n", err)
		return nil, _errGetMessagesFailed
	}
	totalRows, _ := s.Ds.GetMessageCount(conditions)
	resp := base.PageRespFrom(messages, req.Page, req.PageSize, totalRows)
	return (*web.GetMessagesResp)(resp), nil
}

func (s *coreSrv) ReadMessage(req *web.ReadMessageReq) mir.Error {
	message, err := s.Ds.GetMessageByID(req.ID)
	if err != nil {
		return _errReadMessageFailed
	}
	if message.ReceiverUserID != req.Uid {
		return _errNoPermission
	}
	if err = s.Ds.ReadMessage(message); err != nil {
		logrus.Errorf("Ds.ReadMessage err: %s", err)
		return _errReadMessageFailed
	}
	return nil
}

func (s *coreSrv) SendUserWhisper(req *web.SendWhisperReq) mir.Error {
	// 不允许发送私信给自己
	if req.Uid == req.UserID {
		return _errNoWhisperToSelf
	}

	// 今日频次限制
	ctx := context.Background()
	whisperKey := fmt.Sprintf("WhisperTimes:%d", req.Uid)
	if res, _ := s.Redis.Get(ctx, whisperKey).Result(); convert.StrTo(res).MustInt() >= _MaxWhisperNumDaily {
		return _errTooManyWhisperNum
	}

	// 创建私信
	_, err := s.Ds.CreateMessage(&core.Message{
		SenderUserID:   req.Uid,
		ReceiverUserID: req.UserID,
		Type:           core.MsgTypeWhisper,
		Brief:          "给你发送新私信了",
		Content:        req.Content,
	})
	if err != nil {
		logrus.Errorf("Ds.CreateWhisper err: %s", err)
		return _errSendWhisperFailed
	}

	// 写入当日(自然日)计数缓存
	s.Redis.Incr(ctx, whisperKey).Result()
	currentTime := time.Now()
	endTime := time.Date(currentTime.Year(), currentTime.Month(), currentTime.Day(), 23, 59, 59, 0, currentTime.Location())
	s.Redis.Expire(ctx, whisperKey, endTime.Sub(currentTime))

	return nil
}

func (s *coreSrv) GetCollections(req *web.GetCollectionsReq) (*web.GetCollectionsResp, mir.Error) {
	collections, err := s.Ds.GetUserPostCollections(req.UserId, (req.Page-1)*req.PageSize, req.PageSize)
	if err != nil {
		logrus.Errorf("Ds.GetUserPostCollections err: %s", err)
		return nil, _errGetCollectionsFailed
	}
	totalRows, err := s.Ds.GetUserPostCollectionCount(req.UserId)
	if err != nil {
		logrus.Errorf("Ds.GetUserPostCollectionCount err: %s", err)
		return nil, _errGetCollectionsFailed
	}

	var posts []*core.Post
	for _, collection := range collections {
		posts = append(posts, collection.Post)
	}
	postsFormated, err := s.Ds.MergePosts(posts)
	if err != nil {
		logrus.Errorf("Ds.MergePosts err: %s", err)
		return nil, _errGetCollectionsFailed
	}
	resp := base.PageRespFrom(postsFormated, req.Page, req.PageSize, totalRows)

	return (*web.GetCollectionsResp)(resp), nil
}

func (s *coreSrv) UserPhoneBind(req *web.UserPhoneBindReq) mir.Error {
	// 手机重复性检查
	u, err := s.Ds.GetUserByPhone(req.Phone)
	if err == nil && u.Model != nil && u.ID != 0 && u.ID != req.User.ID {
		return _errExistedUserPhone
	}

	// 如果禁止phone verify 则允许通过任意验证码
	if _EnablePhoneVerify {
		c, err := s.Ds.GetLatestPhoneCaptcha(req.Phone)
		if err != nil {
			return _errErrorPhoneCaptcha
		}
		if c.Captcha != req.Captcha {
			return _errErrorPhoneCaptcha
		}
		if c.ExpiredOn < time.Now().Unix() {
			return _errErrorPhoneCaptcha
		}
		if c.UseTimes >= _MaxCaptchaTimes {
			return _errMaxPhoneCaptchaUseTimes
		}
		// 更新检测次数
		s.Ds.UsePhoneCaptcha(c)
	}

	// 执行绑定
	user := req.User
	user.Phone = req.Phone
	if err := s.Ds.UpdateUser(user); err != nil {
		// TODO: 优化错误处理逻辑,失败后上面的逻辑也应该回退
		logrus.Errorf("Ds.UpdateUser err: %s", err)
		return xerror.ServerError
	}
	return nil
}

func (s *coreSrv) GetStars(req *web.GetStarsReq) (*web.GetStarsResp, mir.Error) {
	stars, err := s.Ds.GetUserPostStars(req.UserId, (req.Page-1)*req.PageSize, req.PageSize)
	if err != nil {
		logrus.Errorf("Ds.GetUserPostStars err: %s", err)
		return nil, _errGetStarsFailed
	}
	totalRows, err := s.Ds.GetUserPostStarCount(req.UserId)
	if err != nil {
		logrus.Errorf("Ds.GetUserPostStars err: %s", err)
		return nil, _errGetStarsFailed
	}

	var posts []*core.Post
	for _, star := range stars {
		posts = append(posts, star.Post)
	}
	postsFormated, err := s.Ds.MergePosts(posts)
	if err != nil {
		logrus.Errorf("Ds.MergePosts err: %s", err)
		return nil, _errGetStarsFailed
	}
	resp := base.PageRespFrom(postsFormated, req.Page, req.PageSize, totalRows)

	return (*web.GetStarsResp)(resp), nil
}

func (s *coreSrv) ChangePassword(req *web.ChangePasswordReq) mir.Error {
	// 密码检查
	if err := checkPassword(req.Password); err != nil {
		return err
	}
	// 旧密码校验
	user := req.User
	if !validPassword(user.Password, req.OldPassword, req.User.Salt) {
		return _errErrorOldPassword
	}
	// 更新入库
	user.Password, user.Salt = encryptPasswordAndSalt(req.Password)
	if err := s.Ds.UpdateUser(user); err != nil {
		logrus.Errorf("Ds.UpdateUser err: %s", err)
		return xerror.ServerError
	}
	return nil
}

func (s *coreSrv) SuggestTags(req *web.SuggestTagsReq) (*web.SuggestTagsResp, mir.Error) {
	tags, err := s.Ds.TagsByKeyword(req.Keyword)
	if err != nil {
		logrus.Errorf("Ds.GetTagsByKeyword err: %s", err)
		return nil, xerror.ServerError
	}
	resp := &web.SuggestTagsResp{}
	for _, t := range tags {
		resp.Suggests = append(resp.Suggests, t.Tag)
	}
	return resp, nil
}

func (s *coreSrv) SuggestUsers(req *web.SuggestUsersReq) (*web.SuggestUsersResp, mir.Error) {
	users, err := s.Ds.GetUsersByKeyword(req.Keyword)
	if err != nil {
		logrus.Errorf("Ds.GetUsersByKeyword err: %s", err)
		return nil, xerror.ServerError
	}
	resp := &web.SuggestUsersResp{}
	for _, user := range users {
		resp.Suggests = append(resp.Suggests, user.Username)
	}
	return resp, nil
}

func (s *coreSrv) ChangeNickname(req *web.ChangeNicknameReq) mir.Error {
	if utf8.RuneCountInString(req.Nickname) < 2 || utf8.RuneCountInString(req.Nickname) > 12 {
		return _errNicknameLengthLimit
	}
	user := req.User
	user.Nickname = req.Nickname
	if err := s.Ds.UpdateUser(user); err != nil {
		logrus.Errorf("Ds.UpdateUser err: %s", err)
		return xerror.ServerError
	}
	return nil
}

func (s *coreSrv) ChangeAvatar(req *web.ChangeAvatarReq) (xerr mir.Error) {
	defer func() {
		if xerr != nil {
			deleteOssObjects(s.oss, []string{req.Avatar})
		}
	}()

	if err := s.Ds.CheckAttachment(req.Avatar); err != nil {
		logrus.Errorf("Ds.CheckAttachment failed: %s", err)
		return xerror.InvalidParams
	}
	if err := s.oss.PersistObject(s.oss.ObjectKey(req.Avatar)); err != nil {
		logrus.Errorf("Ds.ChangeUserAvatar persist object failed: %s", err)
		return xerror.ServerError
	}
	user := req.User
	user.Avatar = req.Avatar
	if err := s.Ds.UpdateUser(user); err != nil {
		logrus.Errorf("Ds.UpdateUser failed: %s", err)
		return xerror.ServerError
	}
	return nil
}

func (s *coreSrv) TweetCollectionStatus(req *web.TweetCollectionStatusReq) (*web.TweetCollectionStatusResp, mir.Error) {
	resp := &web.TweetCollectionStatusResp{
		Status: true,
	}
	if _, err := s.Ds.GetUserPostCollection(req.TweetId, req.Uid); err != nil {
		resp.Status = false
		return resp, nil
	}
	return resp, nil
}

func (s *coreSrv) TweetStarStatus(req *web.TweetStarStatusReq) (*web.TweetStarStatusResp, mir.Error) {
	resp := &web.TweetStarStatusResp{
		Status: true,
	}
	if _, err := s.Ds.GetUserPostStar(req.TweetId, req.Uid); err != nil {
		resp.Status = false
		return resp, nil
	}
	return resp, nil
}

func newCoreSrv(s *base.DaoServant, oss core.ObjectStorageService) api.Core {
	return &coreSrv{
		DaoServant: s,
		oss:        oss,
	}
}

func newCoreBinding() api.CoreBinding {
	return &coreBinding{
		UnimplementedCoreBinding: &api.UnimplementedCoreBinding{
			BindAny: base.BindAny,
		},
	}
}

func newCoreRender() api.CoreRender {
	return &coreRender{
		UnimplementedCoreRender: &api.UnimplementedCoreRender{
			RenderAny: base.RenderAny,
		},
	}
}