package com.pingcap.community.blogserver.service;

import com.pingcap.community.blogserver.config.BlogConfig;
import com.pingcap.community.blogserver.config.NoticeConfig;
import com.pingcap.community.blogserver.entity.Comment;
import com.pingcap.community.blogserver.entity.Notification;
import com.pingcap.community.blogserver.entity.Post;
import com.pingcap.community.blogserver.entity.User;
import com.pingcap.community.blogserver.enums.NotificationType;
import com.pingcap.community.blogserver.enums.authority.NotificationAuthority;
import com.pingcap.community.blogserver.exception.notification.NotificationCanNotAccess;
import com.pingcap.community.blogserver.exception.notification.NotificationHaveBeenRead;
import com.pingcap.community.blogserver.exception.notification.NotificationNotFound;
import com.pingcap.community.blogserver.qo.NotificationQuery;
import com.pingcap.community.blogserver.repository.NotificationRepository;
import com.pingcap.community.blogserver.support.lark.message.*;
import com.pingcap.community.blogserver.vo.job.AwardAgainResultVO;
import com.pingcap.community.blogserver.vo.job.AwardResultVO;
import com.pingcap.community.blogserver.vo.notice.NotificationSummary;
import lombok.AllArgsConstructor;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;

import java.text.SimpleDateFormat;
import java.time.Instant;
import java.util.Date;
import java.util.TimeZone;

@Service
@AllArgsConstructor
public class NotificationService {

  private final NotificationRepository notificationRepository;

  private final BlogConfig blogConfig;

  private final NoticeConfig noticeConfig;

  private final LarkClient larkClient;

  private static final SimpleDateFormat sdf;

  private static final String POST_SUBMITTED_LARK_NOTICE_TITLE = "\uD83D\uDE80 有新的文章提交了～";
  private static final String POST_SUBMITTED_LARK_NOTICE_CONTENT = "作者：[%s](%s)\n文章：[%s](%s)\n提交审核时间：%s";

  private static final String POST_PUBLISHED_NOTICE_TITLE = "文章 %s 审核通过，发布成功。基础分奖励 %d 积分&经验值，加成分将于发布后第 31 天后发放，感谢你对 TiDB 社区的贡献~";
  private static final String POST_EXTRA_AWARD_NOTICE_TITLE = "文章 [%s] 加成分 (加成分 = 30 天内的 UV（阅读人次）*1 +收藏数*10 +点赞数*10): %d 积分&经验值已发放，请查收！感谢你对 TiDB 社区的贡献~";
  private static final String POST_REJECTED_NOTICE_TITLE = "文章 [%s] 审核不通过: %s";
  private static final String POST_LIKED_NOTICE_TITLE = "文章 [%s] 被点赞: %s";
  private static final String POST_FAVORITED_NOTICE_TITLE = "%s 收藏了文章 [%s]";
  private static final String POST_RECOMMENDED_NOTICE_TITLE = "文章 [%s] 被置顶";
  private static final String POST_RECOMMENDED_CANCEL_NOTICE_TITLE = "文章 [%s] 取消置顶";
  private static final String POST_COMMENTED_NOTICE_TITLE = "%s 回复了文章 [%s] 下的评论: \"%s\"";
  private static final String POST_DELETED_NOTICE_TITLE = "文章 [%s] 被管理员删除，如有疑问请联系 huangmanshen@pingcap.com";
  private static final String COMMENT_COMMENTED_NOTICE_TITLE = "%s 评论了文章 [%s]: \"%s\"";

  static {
    sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    sdf.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
  }

  public Page<Notification> getUserNotificationList(
    User viewer, NotificationQuery query, Pageable pageable
  ) throws NotificationCanNotAccess {
    if (viewer == null) return null;

    Notification notification = new Notification();
    if (query.getRecipientID() == null || viewer.getId().equals(query.getRecipientID())) {
      notification.setRecipient(new User(viewer.getId()));
    } else if (!viewer.getId().equals(query.getRecipientID()) && viewer.can(NotificationAuthority.READ_OTHERS_NOTIFICATION)) {
      notification.setRecipient(new User(query.getRecipientID()));
    } else {
      throw new NotificationCanNotAccess();
    }

    notification.setHaveRead(query.getHaveRead());
    notification.setType(query.getType());

    Example<Notification> example = Example.of(notification);
    return notificationRepository.findAll(example, pageable);
  }

  public NotificationSummary getNewNotificationSummary(User viewer, Date lastReadDate) {
    if (lastReadDate == null || lastReadDate.before(Date.from(Instant.EPOCH))) {
      lastReadDate = Date.from(Instant.EPOCH);
    }

    Long unreadCount = notificationRepository.countByRecipientAndHaveReadIsFalse(viewer);
    Long newCount = notificationRepository.countByRecipientAndHaveReadIsFalseAndCreatedAtAfter(viewer, lastReadDate);

    NotificationSummary summary = new NotificationSummary();
    summary.setNewCount(newCount);
    summary.setUnreadCount(unreadCount);

    return summary;
  }

  public boolean markNotificationRead(Notification notification) throws NotificationNotFound, NotificationHaveBeenRead {
    if (notification == null) {
      throw new NotificationNotFound();
    }
    if (notification.getHaveRead()) {
      throw new NotificationHaveBeenRead();
    }

    notification.setHaveRead(true);
    notificationRepository.save(notification);
    return true;
  }

  public void sendSystemNotification(Notification notification) {
    notification.setId(null);
    notification.setHaveRead(false);
    notification.setCreatorId(noticeConfig.getDefaultInformerUserID());
    notification.setModifierId(noticeConfig.getDefaultInformerUserID());
    notificationRepository.save(notification);
  }

  public void sendPostSubmitNoticeToLark(String title, String slug, Long userID, String username, Date submitDate) {
    String baseURL = blogConfig.getBaseURL();
    String userURL = String.format("%s/user/%s/posts", baseURL, userID);
    String auditURL = String.format("%s/audits/%s", baseURL, slug);
    String formattedDate = sdf.format(submitDate);
    String noticeContent = String.format(POST_SUBMITTED_LARK_NOTICE_CONTENT,
        username, userURL, title, auditURL, formattedDate);

    larkClient.sendMessage(Message.buildNormalMessageCard(POST_SUBMITTED_LARK_NOTICE_TITLE, noticeContent));
  }

  public void sendPostBePublishedNotice(Post post, AwardResultVO awardResult) {
    User author = post.getAuthor();
    Notification notification = new Notification();
    notification.setTitle(POST_PUBLISHED_NOTICE_TITLE, post.getTitle(), awardResult.getAwardPoints());
    notification.setType(NotificationType.POST);
    notification.setActor(author);
    notification.setRelatedPost(post);
    notification.setRecipient(author);
    sendSystemNotification(notification);
  }

  public void sendPostAwardedAgainNotice(AwardAgainResultVO awardResult) {
    Post post = awardResult.getPost();
    String title = post.getTitle();
    User author = post.getAuthor();

    Notification notification = new Notification();
    notification.setTitle(POST_EXTRA_AWARD_NOTICE_TITLE, title, awardResult.getAgainAwards());
    notification.setType(NotificationType.POST);
    notification.setActor(author);
    notification.setRelatedPost(post);
    notification.setRecipient(author);
    sendSystemNotification(notification);
  }

  public void sendPostBeRejectedNotice(Post post, User actor) {
    Notification notification = new Notification();
    notification.setTitle(POST_REJECTED_NOTICE_TITLE, post.getTitle(), post.getRejectReason());
    notification.setType(NotificationType.POST);
    notification.setActor(actor);
    notification.setRelatedPost(post);
    notification.setRecipient(post.getAuthor());
    sendSystemNotification(notification);
  }

  public void sendPostBeLikedNotice(Post post, User actor) {
    Notification notification = new Notification();
    notification.setType(NotificationType.LIKE);
    notification.setTitle(POST_LIKED_NOTICE_TITLE, actor.getUsername(), post.getTitle());
    notification.setRelatedPost(post);
    notification.setRecipient(post.getAuthor());
    notification.setActor(actor);
    sendSystemNotification(notification);
  }

  public void sendPostBeFavoritedNotice(Post post, User actor) {
    Notification notification = new Notification();
    notification.setType(NotificationType.FAVORITE);
    notification.setTitle(POST_FAVORITED_NOTICE_TITLE, actor.getUsername(), post.getTitle());
    notification.setRelatedPost(post);
    notification.setRecipient(post.getAuthor());
    notification.setActor(actor);
    sendSystemNotification(notification);
  }

  public void sendPostBeRecommendedNotice(Post post, User actor) {
    Notification notification = generatePostNotice(POST_RECOMMENDED_NOTICE_TITLE, post, actor);
    sendSystemNotification(notification);
  }

  public void sendPostBeCancelRecommendedNotice(Post post, User actor) {
    Notification notification = generatePostNotice(POST_RECOMMENDED_CANCEL_NOTICE_TITLE, post, actor);
    sendSystemNotification(notification);
  }

  public void sendPostBeDeletedNotice(Post post, User actor) {
    Notification notification = generatePostNotice(POST_DELETED_NOTICE_TITLE, post, actor);
    sendSystemNotification(notification);
  }

  private Notification generatePostNotice(String titleTemplate, Post post, User actor) {
    Notification notification = new Notification();
    notification.setType(NotificationType.POST);
    notification.setTitle(titleTemplate, post.getTitle());
    notification.setRelatedPost(post);
    notification.setRecipient(post.getAuthor());
    notification.setActor(actor);
    return notification;
  }

  /**
   * Send notice when the post or comment of post being commented.
   * @param post The post that be replied by the new comment.
   * @param comment The new comment.
   * @param repliedComment The comment that be replied by the new comment.
   * @param actor The commenter.
   */
  public void sendPostBeCommentedNotice(
      Post post, Comment comment, Comment repliedComment, User actor
  ) {
    if (repliedComment == null) {
      // Ignore comment to own post.
      if (actor.equals(post.getAuthor())) {
        return;
      }
    } else {
      // Ignore comment to own comment.
      if (actor.equals(repliedComment.getCommenter())) {
        return;
      }
      // Notice: avoid loading more replied comments.
      repliedComment.setReplyComment(null);
    }

    String commentText = comment.getContent();
    if (commentText.length() > 60) {
      commentText = commentText.substring(0, 60);
    }

    Notification notification = new Notification();
    notification.setActor(actor);
    notification.setType(NotificationType.COMMENT);
    notification.setRelatedPost(post);
    notification.setRelatedComment(comment);

    if (repliedComment != null) {
      // If the comment is a reply to another comment, notification will be
      // sent to the author of the replied comment.
      notification.setRecipient(repliedComment.getCommenter());
      notification.setTitle(POST_COMMENTED_NOTICE_TITLE, actor.getUsername(), post.getTitle(), commentText);
    } else {
      // Otherwise, notification will be sent to the author of the post.
      notification.setRecipient(post.getAuthor());
      notification.setTitle(COMMENT_COMMENTED_NOTICE_TITLE, actor.getUsername(), post.getTitle(), commentText);
    }
    sendSystemNotification(notification);
  }

}
