/**
 * 
 */
package de.bstly.board.businesslogic;

import java.util.List;

import org.apache.commons.lang3.RandomStringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
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.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import com.google.common.collect.Lists;

import de.bstly.board.model.Entry;
import de.bstly.board.model.LocalUser;
import de.bstly.board.model.QEntry;
import de.bstly.board.model.QLocalUser;
import de.bstly.board.repository.EntryRepository;
import de.bstly.board.repository.LocalUserRepository;
import de.bstly.board.security.LocalUserDetails;

/**
 * The Class UserManager.
 */
@Service
public class UserManager implements UserDetailsService, SmartInitializingSingleton {

	private Logger logger = LoggerFactory.getLogger(UserManager.class);

	@Autowired
	private LocalUserRepository localUserRepository;
	@Autowired
	private PasswordEncoder passwordEncoder;
	@Autowired
	private EntryManager entryManager;
	@Autowired
	private EntryRepository entryRepository;
	private QLocalUser qLocalUser = QLocalUser.localUser;
	private QEntry qEntry = QEntry.entry;

	@Value("${admin.password:}")
	private String adminPassword;

	/*
	 * @see org.springframework.security.core.userdetails.UserDetailsService#
	 * loadUserByUsername(java.lang.String)
	 */
	/*
	 * @see org.springframework.security.core.userdetails.UserDetailsService#loadUserByUsername(java.lang.String)
	 */
	/*
	 * @see org.springframework.security.core.userdetails.UserDetailsService#loadUserByUsername(java.lang.String)
	 */
	/*
	 * @see org.springframework.security.core.userdetails.UserDetailsService#loadUserByUsername(java.lang.String)
	 */
	/*
	 * @see org.springframework.security.core.userdetails.UserDetailsService#loadUserByUsername(java.lang.String)
	 */
	/*
	 * @see org.springframework.security.core.userdetails.UserDetailsService#loadUserByUsername(java.lang.String)
	 */
	/*
	 * @see org.springframework.security.core.userdetails.UserDetailsService#loadUserByUsername(java.lang.String)
	 */
	/*
	 * @see org.springframework.security.core.userdetails.UserDetailsService#loadUserByUsername(java.lang.String)
	 */
	/*
	 * @see org.springframework.security.core.userdetails.UserDetailsService#loadUserByUsername(java.lang.String)
	 */
	/*
	 * @see org.springframework.security.core.userdetails.UserDetailsService#loadUserByUsername(java.lang.String)
	 */
	/*
	 * @see org.springframework.security.core.userdetails.UserDetailsService#loadUserByUsername(java.lang.String)
	 */
	/*
	 * @see org.springframework.security.core.userdetails.UserDetailsService#loadUserByUsername(java.lang.String)
	 */
	/*
	 * @see org.springframework.security.core.userdetails.UserDetailsService#loadUserByUsername(java.lang.String)
	 */
	/*
	 * @see org.springframework.security.core.userdetails.UserDetailsService#loadUserByUsername(java.lang.String)
	 */
	/*
	 * @see org.springframework.security.core.userdetails.UserDetailsService#loadUserByUsername(java.lang.String)
	 */
	/*
	 * @see org.springframework.security.core.userdetails.UserDetailsService#loadUserByUsername(java.lang.String)
	 */
	/*
	 * @see org.springframework.security.core.userdetails.UserDetailsService#loadUserByUsername(java.lang.String)
	 */
	/*
	 * @see org.springframework.security.core.userdetails.UserDetailsService#loadUserByUsername(java.lang.String)
	 */
	/*
	 * @see org.springframework.security.core.userdetails.UserDetailsService#loadUserByUsername(java.lang.String)
	 */
	/*
	 * @see org.springframework.security.core.userdetails.UserDetailsService#loadUserByUsername(java.lang.String)
	 */
	/*
	 * @see org.springframework.security.core.userdetails.UserDetailsService#loadUserByUsername(java.lang.String)
	 */
	/*
	 * @see org.springframework.security.core.userdetails.UserDetailsService#loadUserByUsername(java.lang.String)
	 */
	/*
	 * @see org.springframework.security.core.userdetails.UserDetailsService#loadUserByUsername(java.lang.String)
	 */
	/*
	 * @see org.springframework.security.core.userdetails.UserDetailsService#loadUserByUsername(java.lang.String)
	 */
	/*
	 * @see org.springframework.security.core.userdetails.UserDetailsService#loadUserByUsername(java.lang.String)
	 */
	/*
	 * @see org.springframework.security.core.userdetails.UserDetailsService#loadUserByUsername(java.lang.String)
	 */
	/*
	 * @see org.springframework.security.core.userdetails.UserDetailsService#loadUserByUsername(java.lang.String)
	 */
	/*
	 * @see org.springframework.security.core.userdetails.UserDetailsService#loadUserByUsername(java.lang.String)
	 */
	/*
	 * @see org.springframework.security.core.userdetails.UserDetailsService#loadUserByUsername(java.lang.String)
	 */
	/*
	 * @see org.springframework.security.core.userdetails.UserDetailsService#
	 * loadUserByUsername(java.lang.String)
	 */
	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		LocalUser localUser = getByUsername(username);

		if (localUser == null) {
			throw new UsernameNotFoundException(username);
		}

		List<GrantedAuthority> authorities = Lists.newArrayList();
		if (localUser.getRoles() != null) {
			for (String role : localUser.getRoles()) {
				authorities.add(new SimpleGrantedAuthority(role));
			}
		}

		String passwordHash = localUser.getPasswordHash();

		if (passwordHash == null) {
			passwordHash = "";
		}

		LocalUserDetails userDetails = new LocalUserDetails(username, passwordHash, authorities);
		return userDetails;
	}

	/*
	 * @see org.springframework.beans.factory.SmartInitializingSingleton#
	 * afterSingletonsInstantiated()
	 */
	/*
	 * @see org.springframework.beans.factory.SmartInitializingSingleton#afterSingletonsInstantiated()
	 */
	/*
	 * @see org.springframework.beans.factory.SmartInitializingSingleton#afterSingletonsInstantiated()
	 */
	/*
	 * @see org.springframework.beans.factory.SmartInitializingSingleton#afterSingletonsInstantiated()
	 */
	/*
	 * @see org.springframework.beans.factory.SmartInitializingSingleton#afterSingletonsInstantiated()
	 */
	/*
	 * @see org.springframework.beans.factory.SmartInitializingSingleton#afterSingletonsInstantiated()
	 */
	/*
	 * @see org.springframework.beans.factory.SmartInitializingSingleton#afterSingletonsInstantiated()
	 */
	/*
	 * @see org.springframework.beans.factory.SmartInitializingSingleton#afterSingletonsInstantiated()
	 */
	/*
	 * @see org.springframework.beans.factory.SmartInitializingSingleton#afterSingletonsInstantiated()
	 */
	/*
	 * @see org.springframework.beans.factory.SmartInitializingSingleton#afterSingletonsInstantiated()
	 */
	/*
	 * @see org.springframework.beans.factory.SmartInitializingSingleton#afterSingletonsInstantiated()
	 */
	/*
	 * @see org.springframework.beans.factory.SmartInitializingSingleton#afterSingletonsInstantiated()
	 */
	/*
	 * @see org.springframework.beans.factory.SmartInitializingSingleton#afterSingletonsInstantiated()
	 */
	/*
	 * @see org.springframework.beans.factory.SmartInitializingSingleton#afterSingletonsInstantiated()
	 */
	/*
	 * @see org.springframework.beans.factory.SmartInitializingSingleton#afterSingletonsInstantiated()
	 */
	/*
	 * @see org.springframework.beans.factory.SmartInitializingSingleton#afterSingletonsInstantiated()
	 */
	/*
	 * @see org.springframework.beans.factory.SmartInitializingSingleton#afterSingletonsInstantiated()
	 */
	/*
	 * @see org.springframework.beans.factory.SmartInitializingSingleton#afterSingletonsInstantiated()
	 */
	/*
	 * @see org.springframework.beans.factory.SmartInitializingSingleton#afterSingletonsInstantiated()
	 */
	/*
	 * @see org.springframework.beans.factory.SmartInitializingSingleton#afterSingletonsInstantiated()
	 */
	/*
	 * @see org.springframework.beans.factory.SmartInitializingSingleton#afterSingletonsInstantiated()
	 */
	/*
	 * @see org.springframework.beans.factory.SmartInitializingSingleton#afterSingletonsInstantiated()
	 */
	/*
	 * @see org.springframework.beans.factory.SmartInitializingSingleton#afterSingletonsInstantiated()
	 */
	/*
	 * @see org.springframework.beans.factory.SmartInitializingSingleton#afterSingletonsInstantiated()
	 */
	/*
	 * @see org.springframework.beans.factory.SmartInitializingSingleton#afterSingletonsInstantiated()
	 */
	/*
	 * @see org.springframework.beans.factory.SmartInitializingSingleton#afterSingletonsInstantiated()
	 */
	/*
	 * @see org.springframework.beans.factory.SmartInitializingSingleton#afterSingletonsInstantiated()
	 */
	/*
	 * @see org.springframework.beans.factory.SmartInitializingSingleton#afterSingletonsInstantiated()
	 */
	/*
	 * @see org.springframework.beans.factory.SmartInitializingSingleton#afterSingletonsInstantiated()
	 */
	/*
	 * @see org.springframework.beans.factory.SmartInitializingSingleton#
	 * afterSingletonsInstantiated()
	 */
	@Override
	public void afterSingletonsInstantiated() {
		if (!localUserRepository.exists(qLocalUser.roles.contains("ROLE_ADMIN"))) {
			if (!StringUtils.hasText(adminPassword)) {
				adminPassword = RandomStringUtils.random(24, true, true);
				logger.error("password for 'admin': "
						+ adminPassword);
			}
			LocalUser admin = new LocalUser();
			admin.setUsername("admin");
			admin.setRoles(Lists.newArrayList("ROLE_ADMIN"));
			admin.setPasswordHash(passwordEncoder.encode(adminPassword));
			admin.setLocale("en");
			localUserRepository.save(admin);
		}
	}

	/**
	 * Gets the by username.
	 *
	 * @param username the username
	 * @return the by username
	 */
	public LocalUser getByUsername(String username) {
		return localUserRepository.findOne(qLocalUser.username.equalsIgnoreCase(username))
				.orElse(null);
	}

	/**
	 * Gets the by external id.
	 *
	 * @param externalId the external id
	 * @return the by external id
	 */
	public LocalUser getByExternalId(String externalId) {
		return localUserRepository.findOne(qLocalUser.externalId.eq(externalId)).orElse(null);
	}

	/**
	 * Gets the by auth.
	 *
	 * @param authentication the authentication
	 * @return the by auth
	 */
	public LocalUser getByAuth(Authentication authentication) {
		if (authentication != null) {
			if (authentication instanceof UsernamePasswordAuthenticationToken) {
				UsernamePasswordAuthenticationToken token = (UsernamePasswordAuthenticationToken) authentication;
				return getByUsername(token.getName());
			} else if (authentication instanceof OAuth2AuthenticationToken) {
				OAuth2AuthenticationToken token = (OAuth2AuthenticationToken) authentication;
				String externalId = token.getAuthorizedClientRegistrationId()
						+ "-"
						+ token.getName();
				LocalUser localUser = getByExternalId(externalId);
				if (localUser == null) {
					localUser = new LocalUser();
					localUser.setExternalId(externalId);
					String tmpUsername = token.getPrincipal().getAttribute("preferred_username");
					if (!StringUtils.hasText(tmpUsername)) {
						tmpUsername = token.getPrincipal().getAttribute("username");
					}
					if (!StringUtils.hasText(tmpUsername)) {
						tmpUsername = token.getPrincipal().getAttribute("name");
					}
					if (!StringUtils.hasText(tmpUsername)) {
						tmpUsername = token.getName();
					}
					int count = 1;
					String username = tmpUsername;
					while (localUserRepository
							.exists(qLocalUser.username.equalsIgnoreCase(username))) {
						username = tmpUsername
								+ "-"
								+ count;
						count++;
					}

					localUser.setUsername(username);
					localUser.setEmail(token.getPrincipal().getAttribute("email"));

					String locale = token.getPrincipal().getAttribute("locale");
					if (!StringUtils.hasText(locale)) {
						locale = "de";
					}
					localUser.setLocale(locale);

					String darkTheme = token.getPrincipal().getAttribute("darkTheme");
					if (!StringUtils.hasText(darkTheme)) {
						darkTheme = "false";
					}
					localUser.setDarkTheme(Boolean.valueOf(darkTheme));

					localUser = localUserRepository.save(localUser);
				}
				return localUser;
			}
		}
		return null;
	}

	/**
	 * Apply metadata.
	 *
	 * @param username the username
	 * @param user the user
	 */
	public void applyMetadata(String username, LocalUser user) {
		if (user.getUsername().equalsIgnoreCase(username)
				&& !user.getMetadata().containsKey("self")) {
			user.getMetadata().put("self", true);
		}

		if (!user.getMetadata().containsKey("points")) {
			user.getMetadata().put("points", getKarma(user.getUsername()));
		}
	}

	/**
	 * Gets the karma.
	 *
	 * @param username the username
	 * @return the karma
	 */
	public long getKarma(String username) {
		long karma = 0;
		for (Entry entry : entryRepository.findAll(qEntry.author.equalsIgnoreCase(username))) {
			karma += entryManager.getUserPoints(entry.getId(), username);
		}
		return karma;
	}

	/**
	 * Save.
	 *
	 * @param localUser the local user
	 * @return the local user
	 */
	public LocalUser save(LocalUser localUser) {
		return localUserRepository.save(localUser);
	}

	/**
	 * Exists.
	 *
	 * @param username the username
	 * @return true, if successful
	 */
	public boolean exists(String username) {
		return localUserRepository.exists(qLocalUser.username.equalsIgnoreCase(username));
	}

}
