package com.pingcap.community.blogserver.config;

import com.pingcap.community.blogserver.entity.Authority;
import com.pingcap.community.blogserver.entity.User;
import com.pingcap.community.blogserver.entity.UserAccessToken;
import com.pingcap.community.blogserver.exception.user.UserNotFound;
import com.pingcap.community.blogserver.repository.AuthorityRepository;
import com.pingcap.community.blogserver.repository.RoleRepository;
import com.pingcap.community.blogserver.repository.UserAccessTokenRepository;
import com.pingcap.community.blogserver.repository.UserRepository;
import com.pingcap.community.blogserver.support.security.AccessTokenConfiguration;
import com.pingcap.community.blogserver.support.security.TidbCommunityUserDetailsService;
import com.pingcap.community.blogserver.support.sso.TidbCommunityUserInfo;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.util.DigestUtils;

import javax.persistence.EntityManager;
import javax.transaction.Transactional;
import java.util.Collections;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;

@Component
@AllArgsConstructor
public class BlogUserDetailsService implements TidbCommunityUserDetailsService<User> {
  private final UserRepository userRepository;
  private final RoleRepository roleRepository;
  private final AuthorityRepository authorityRepository;
  private final UserAccessTokenRepository userAccessTokenRepository;
  private final EntityManager em;
  private final AccessTokenConfiguration accessTokenConfiguration;

  @Transactional
  @Override
  public Optional<User> findUserDetails(TidbCommunityUserInfo userInfo) {
    return userRepository.findByOpenid(userInfo.getOpenid());
  }

  @Transactional
  @Override
  public User findUserDetailsByAccessToken(String accessToken) throws UserNotFound {
    String salt = Optional.ofNullable(accessTokenConfiguration.getSalt()).orElse("salt");
    String encodedAccessToken = DigestUtils.md5DigestAsHex(String.format("%s-%s", accessToken, salt).getBytes());
    Optional<UserAccessToken> accessTokenOptional = userAccessTokenRepository.findByAccessToken(encodedAccessToken);
    Optional<User> userOptional = accessTokenOptional.map(UserAccessToken::getUser);
    if (userOptional.isEmpty()) {
      throw new UserNotFound();
    }

    return userOptional.get();
  }

  @Override
  public User createUser(TidbCommunityUserInfo userInfo) {
    var user = new User();
    user.setOpenid(userInfo.getOpenid());
    user.setCreatorId(1L);
    user.setModifierId(1L);
    user.setRoles(Collections.singleton(roleRepository.getByName("READER")));
    fillInUserInfo(userInfo, user);
    return userRepository.save(user);
  }

  @Override
  public User updateUser(User user, TidbCommunityUserInfo userInfo) {
    fillInUserInfo(userInfo, user);
    return userRepository.save(user);
  }

  private void fillInUserInfo(TidbCommunityUserInfo userInfo, User user) {
    user.setUsername(userInfo.getUsername());
    user.setIntroduction(userInfo.getBio());
    user.setAvatarURL(userInfo.getAvatarUrl());
    user.setName(userInfo.getName() == null ? "" : userInfo.getName());
    user.setCompanyName(userInfo.getCompanyName());
    user.setPhoneNumber(userInfo.getPhone());
  }

  @Override
  public void resolveAuthorities(User user) {
    em.detach(user);
    Set<Authority> authorities = new HashSet<>(Optional.ofNullable(user.getAuthorities()).orElse(Collections.emptySet()));
    Set<Authority> roleAuthorities = authorityRepository.findByRolesIn(user.getRoles());
    authorities.addAll(roleAuthorities);
    user.setAuthorities(authorities);
  }
}
