package com.pingcap.community.blogserver.entity;

import com.fasterxml.jackson.annotation.JsonView;
import com.pingcap.community.blogserver.support.security.UserDetails;
import lombok.*;
import org.hibernate.Hibernate;
import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.Where;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;

import javax.persistence.*;
import java.util.Date;
import java.util.Objects;
import java.util.Set;

@Entity(name = "user")
@Table(name = "users")
@SQLDelete(sql = "UPDATE users SET deleted_at = now() WHERE id = ?")
@Where(clause = "deleted_at is null")
@NamedEntityGraph(name = "withAuthorities", attributeNodes = {@NamedAttributeNode("authorities"),@NamedAttributeNode("roles")})
@Getter
@Setter
@ToString
@NoArgsConstructor
@AllArgsConstructor
@EntityListeners(AuditingEntityListener.class)
public class User implements UserDetails {

  public interface UserLoginView {}

  public interface UserLoginWithOpenIDView {}

  public interface UserBriefView {}

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

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name = "id", nullable = false, updatable = false)
  @JsonView({UserLoginView.class, UserBriefView.class, UserLoginWithOpenIDView.class})
  private Long id;

  @Column(name = "username", nullable = false, length = 150)
  @JsonView({UserLoginView.class, UserBriefView.class, UserLoginWithOpenIDView.class})
  private String username;

  @Column(name = "name", length = 128)
  private String name;

  @Column(name ="phone_number", length = 32)
  private String phoneNumber;

  @Column(name ="company_name", length = 100)
  private String companyName;

  @JsonView(UserBriefView.class)
  private String introduction;

  @Column(name = "avatar_url", nullable = false)
  @JsonView({UserLoginView.class, UserBriefView.class, UserLoginWithOpenIDView.class})
  private String avatarURL;

  @Column(name = "openid", unique = true, nullable = false)
  @JsonView(UserLoginWithOpenIDView.class)
  private String openid;

  @Column(name = "enable", insertable = false, columnDefinition = "bit(1) default b'1' not null")
  @JsonView
  private Boolean enable;

  @ManyToMany
  @JoinTable(
    name = "user_roles",
    joinColumns = @JoinColumn(name = "user_id"),
    inverseJoinColumns = @JoinColumn(name = "role_id")
  )
  @ToString.Exclude
  @JsonView
  private Set<Role> roles;

  @ManyToMany
  @JoinTable(
    name = "user_authorities",
    joinColumns = @JoinColumn(name = "user_id"),
    inverseJoinColumns = @JoinColumn(name = "authority_id")
  )
  @ToString.Exclude
  @JsonView
  private Set<Authority> authorities;

  @CreatedBy
  @Column(name = "creator_id", updatable = false, nullable = false)
  @JsonView
  private Long creatorId;

  @CreatedDate
  @Column(name = "created_at", updatable = false, columnDefinition = "datetime not null default CURRENT_TIMESTAMP")
  @JsonView
  private Date createdAt;

  @LastModifiedBy
  @Column(name = "modifier_id", nullable = false)
  @JsonView
  private Long modifierId;

  @LastModifiedDate
  @Column(name = "last_modified_at", columnDefinition = "datetime not null default CURRENT_TIMESTAMP")
  @JsonView
  private Date lastModifiedAt;

  @Column(name = "deleted_at", insertable = false)
  @JsonView
  private Date deletedAt;

  public boolean can(GrantedAuthority authority) {
    if (authority == null) {
      return false;
    }
    return this.authorities.stream().map(GrantedAuthority::getAuthority).anyMatch((a) -> Objects.equals(a, authority.getAuthority()));
  }

  public boolean can(String authority) {
    return can(new SimpleGrantedAuthority(authority));
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) return false;
    User user = (User) o;
    return id != null && Objects.equals(id, user.id);
  }

  @Override
  public int hashCode() {
    return getClass().hashCode();
  }

}
