package com.pingcap.community.blogserver.entity;

import com.fasterxml.jackson.annotation.JsonView;
import com.pingcap.community.blogserver.enums.authority.PostAuthority;
import com.pingcap.community.blogserver.enums.PostOrigin;
import com.pingcap.community.blogserver.enums.PostStatus;
import com.pingcap.community.blogserver.support.acl.sdr.IRestAclEntity;
import com.pingcap.community.blogserver.support.action.Statusful;
import com.pingcap.community.blogserver.util.UserAuthorityUtils;
import lombok.*;
import org.hibernate.annotations.*;
import org.springframework.data.rest.core.annotation.RestResource;
import org.springframework.http.HttpMethod;
import org.springframework.security.core.Authentication;

import javax.persistence.*;
import javax.persistence.Entity;
import javax.persistence.Table;
import java.util.Date;
import java.util.List;
import java.util.Set;

@Entity(name = "post")
@Table(name = "posts")
@SQLDelete(sql = "UPDATE posts SET deleted_at = now(), status = 'DELETED' WHERE id = ? and version = ?")
@Where(clause = "deleted_at is null")
@Getter
@Setter
@ToString
@NoArgsConstructor
@AllArgsConstructor
@SecondaryTable(name = "post_infos", pkJoinColumns = @PrimaryKeyJoinColumn(name = "post_id", referencedColumnName = "id"))
public class Post extends BaseEntity implements IRestAclEntity, Statusful<PostStatus> {

  public interface PostDetailWebhookView extends User.UserLoginWithOpenIDView, PostDetailView {}

  public interface PostDetailView extends User.UserLoginView, Category.CategoryItemView, Tag.TagItemView, BaseEntity.TimeInfoView {}

  public interface PostItemView extends User.UserLoginView, Category.CategoryItemView, Tag.TagItemView, BaseEntity.TimeInfoView {}

  public interface PostTitleView {}

  public Post(Long id) {
    this.id = id;
  }

  public Post(Long id, String title, String content, Long rawTextVersion, Long version) {
    this.id = id;
    this.title = title;
    this.content = content;
    this.rawTextVersion = rawTextVersion;
    this.version = version;
  }

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name = "id", nullable = false)
  @JsonView({ PostDetailView.class, PostItemView.class, PostTitleView.class })
  private Long id;

  @Column(name = "slug", length = 128)
  @JsonView({ PostDetailView.class, PostItemView.class, PostTitleView.class })
  private String slug;

  @Column(name = "version", nullable = false, columnDefinition = "bigint default 1")
  @JsonView({ PostDetailView.class })
  @Version
  private Long version;

  @Column(name = "title", nullable = false)
  @JsonView({ PostDetailView.class, PostItemView.class, PostTitleView.class })
  private String title;

  @Lob
  @Column(name = "content")
  @JsonView({ PostDetailView.class })
  private String content;

  @JsonView({ PostDetailView.class })
  @Transient
  private String summary;

  @Lob
  @Column(name = "raw_text", table = "post_infos")
  @JsonView(PostDetailView.class)
  private String rawText;

  @Column(name = "raw_text_version", table = "post_infos")
  @JsonView(PostDetailView.class)
  private Long rawTextVersion;

  @Lob
  @Column(name = "markdown_text", table = "post_infos")
  private String markdownText;

  @Column(name = "markdown_text_version", table = "post_infos")
  private Long markdownTextVersion;

  @Lob
  @Column(name = "html_text", table = "post_infos")
  private String htmlText;

  @Column(name = "html_text_version", table = "post_infos")
  private Long htmlTextVersion;

  @Column(name = "score", table = "post_infos")
  @JsonView(PostDetailView.class)
  private Long score;

  @Enumerated(EnumType.STRING)
  @Column(name = "status", nullable = false)
  @JsonView({ PostDetailView.class, PostItemView.class })
  private PostStatus status;

  @Enumerated(EnumType.STRING)
  @Column(name = "origin", nullable = false)
  @JsonView({ PostDetailView.class, PostItemView.class })
  private PostOrigin origin;

  @Column(name = "source_url", length = 512)
  @JsonView({ PostDetailView.class })
  private String sourceURL;

  @JsonView({ PostDetailView.class })
  @Transient
  private String rejectReason;

  @Column(name = "cover_image_url", length = 512)
  private String coverImageURL;

  @ManyToOne(optional = false)
  @JoinColumn(name = "author_id", nullable = false)
  @RestResource(exported = false)
  @JsonView({ PostDetailView.class, PostItemView.class })
  private User author;

  @ManyToOne
  @JoinColumn(name = "category_id")
  @RestResource(exported = false)
  @JsonView({ PostDetailView.class, PostItemView.class })
  private Category category;

  @ManyToMany(fetch = FetchType.EAGER)
  @JoinTable(
    name = "post_tags",
    joinColumns = @JoinColumn(name = "post_id"),
    inverseJoinColumns = @JoinColumn(name = "tag_id")
  )
  @JsonView({ PostDetailView.class, PostItemView.class })
  @RestResource(exported = false)
  private Set<Tag> tags;

  @OneToMany(mappedBy = "post")
  @ToString.Exclude
  private List<Comment> comments;

  // The recommended post will be displayed at the top of the blog homepage.
  @Column(name = "recommended", insertable = false)
  @JsonView({ PostDetailView.class, PostItemView.class  })
  private Boolean recommended;

  @ManyToOne
  @JoinColumn(name = "recommender_id", insertable = false)
  @RestResource(exported = false)
  @JsonView({ PostDetailView.class, PostItemView.class  })
  private User recommender;

  // When multiple posts are recommended at the top, the posts can be sorted according to
  // the recommended time.
  @Column(name = "recommended_at", insertable = false)
  @JsonView({ PostDetailView.class })
  private Date recommendAt;

  @ManyToOne
  @JoinColumn(name = "last_publisher_id", insertable = false)
  @RestResource(exported = false)

  @JsonView({ PostDetailView.class, PostItemView.class  })
  private User lastPublisher;

  @Column(name = "published_at", insertable = false)
  @JsonView({ PostDetailView.class, PostItemView.class })
  private Date publishedAt;

  @Column(name = "last_published_at", insertable = false)
  @JsonView({ PostDetailView.class, PostItemView.class  })
  private Date lastPublishedAt;

  public String getShortTitle(int maxLength) {
    return title.length() > maxLength ? title.substring(0, maxLength).concat("...") : title;
  }

  public boolean isPublic() {
    return this.getStatus().equals(PostStatus.PUBLISHED);
  }

  public boolean isAuthor(User user) {
    if (user == null) {
      return false;
    } else {
      return this.author.equals(user);
    }
  }

  @Override
  public boolean hasPermission(Authentication authentication, HttpMethod accessMethod) {
    // The public post can be viewed by everyone.
    if (accessMethod.equals(HttpMethod.GET) && isPublic()) {
      return true;
    }
    if (authentication.isAuthenticated()) {
      // The author of the post has full control over the post.
      Object principal = authentication.getPrincipal();
      if (principal instanceof User user && isAuthor(user)) {
        return switch (accessMethod) {
          case GET -> UserAuthorityUtils.hasAuthority(authentication, PostAuthority.READ_POST);
          case PATCH, PUT -> UserAuthorityUtils.hasAuthority(authentication, PostAuthority.MODIFY_POST);
          case DELETE -> UserAuthorityUtils.hasAuthority(authentication, PostAuthority.DELETE_POST);
          default -> false;
        };
      }

      // Others except the author should have -OTHERS- permission to operate on the post.
      return switch (accessMethod) {
        case GET -> UserAuthorityUtils.hasAuthority(authentication, PostAuthority.READ_OTHERS_POST);
        case PATCH, PUT -> UserAuthorityUtils.hasAuthority(authentication, PostAuthority.MODIFY_OTHERS_POST);
        case DELETE -> UserAuthorityUtils.hasAuthority(authentication, PostAuthority.DELETE_OTHERS_POST);
        default -> false;
      };
    }
    return false;
  }

}
