package com.oguerreiro.resilient.security;

import java.util.List;
import java.util.Locale;
import java.util.concurrent.atomic.AtomicBoolean;

import org.hibernate.Hibernate;
import org.hibernate.validator.internal.constraintvalidators.hv.EmailValidator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import com.oguerreiro.resilient.domain.Authority;
import com.oguerreiro.resilient.domain.Organization;
import com.oguerreiro.resilient.domain.User;
import com.oguerreiro.resilient.repository.OrganizationRepository;
import com.oguerreiro.resilient.repository.UserRepository;
import com.oguerreiro.resilient.repository.security.SecurityGroupRepository;
import com.oguerreiro.resilient.security.custom.ResilientUserDetails;
import com.oguerreiro.resilient.security.resillient.SecurityGroup;

/**
 * Authenticate a user from the database.
 */
@Component("userDetailsService")
public class DomainUserDetailsService implements UserDetailsService {

  private final Logger log = LoggerFactory.getLogger(DomainUserDetailsService.class);

  private final UserRepository userRepository;
  private final OrganizationRepository organizationRepository;
  private final SecurityGroupRepository securityGroupRepository;

  public DomainUserDetailsService(UserRepository userRepository, OrganizationRepository organizationRepository,
      SecurityGroupRepository securityGroupRepository) {
    this.userRepository = userRepository;
    this.organizationRepository = organizationRepository;
    this.securityGroupRepository = securityGroupRepository;
  }

  /*
  @Override
  @Transactional(readOnly = true)
  public UserDetails loadUserByUsername(final String login) {
    log.debug("Authenticating {}", login);
  
    if (new EmailValidator().isValid(login, null)) {
      return userRepository.findOneWithAuthoritiesByEmailIgnoreCase(login).map(
          user -> createSpringSecurityUser(login, user)).orElseThrow(
              () -> new UsernameNotFoundException("User with email " + login + " was not found in the database"));
    }
  
    String lowercaseLogin = login.toLowerCase(Locale.ENGLISH);
    return userRepository.findOneWithAuthoritiesByLogin(lowercaseLogin).map(
        user -> createSpringSecurityUser(lowercaseLogin, user)).orElseThrow(
            () -> new UsernameNotFoundException("User " + lowercaseLogin + " was not found in the database"));
  }
  */

  @Override
  @Transactional(readOnly = true)
  public UserDetails loadUserByUsername(final String login) {
    log.debug("Authenticating {}", login);

    UserDetails userDetails = null;
    AtomicBoolean allowUserPwdAuthentication = new AtomicBoolean(false);

    if (new EmailValidator().isValid(login, null)) {
      //@formatter:off
      userDetails = userRepository.findOneWithAuthoritiesByEmailIgnoreCase(login)
          .map(user -> {
            allowUserPwdAuthentication.set(user.getAllowUserPwdAuthentication());
            return createResilientSecurityUser(login, user); 
          })
          .orElseThrow(
              () -> new UsernameNotFoundException("User with email " + login + " was not found in the database")
          );
      //@formatter:on
    } else {
      String lowercaseLogin = login.toLowerCase(Locale.ENGLISH);
      //@formatter:off
      userDetails = userRepository.findOneWithAuthoritiesByLogin(lowercaseLogin)
          .map(user -> { 
            allowUserPwdAuthentication.set(user.getAllowUserPwdAuthentication());
            return createResilientSecurityUser(lowercaseLogin, user);
          })
          .orElseThrow(
              () -> new UsernameNotFoundException("User " + lowercaseLogin + " was not found in the database")
          );
      //@formatter:on
    }

    if (userDetails == null) {
      throw new UsernameNotFoundException("User " + login + " was not found in the database");
    }

    if (!allowUserPwdAuthentication.get()) {
      // The user can't login with User + Password
      throw new UsernameNotFoundException("User " + login + " not allowed for login with User + Password.");
    }

    return userDetails;
  }

  private org.springframework.security.core.userdetails.User createSpringSecurityUser(String lowercaseLogin,
      User user) {
    if (!user.isActivated()) {
      throw new UserNotActivatedException("User " + lowercaseLogin + " was not activated");
    }
    List<SimpleGrantedAuthority> grantedAuthorities = user.getAuthorities().stream().map(Authority::getName).map(
        SimpleGrantedAuthority::new).toList();

    return new org.springframework.security.core.userdetails.User(user.getLogin(), user.getPassword(),
        grantedAuthorities);
  }

  private ResilientUserDetails createResilientSecurityUser(String lowercaseLogin, User user) {
    if (!user.isActivated()) {
      throw new UserNotActivatedException("User " + lowercaseLogin + " was not activated");
    }

    List<SimpleGrantedAuthority> grantedAuthorities = user.getAuthorities().stream().map(Authority::getName).map(
        SimpleGrantedAuthority::new).toList();

    //Find the user in the organization
    Organization parentOrg = null;
    Organization person = organizationRepository.findOneByUser(user).orElse(null);
    if (person != null) {
      parentOrg = person.getParent();
      Hibernate.initialize(parentOrg); // explicitly loads the proxy. I need to guarantee that this is NEVER a uninitialized proxy
    }
    // Find the SecurityGroup for permissions
    // SecurityGroup securityGroup = this.securityGroupRepository.findOneWithEagerRelationships(4L).orElse(null);  // TODO: Replace with more functional code, this is just testing
    SecurityGroup securityGroup = user.getSecurityGroup();

    return new ResilientUserDetails(user.getLogin(), user.getPassword(), grantedAuthorities, securityGroup, parentOrg,
        user.getLangKey());
  }
}
