package info.bukova.isspst.services.users;

import info.bukova.isspst.Constants;
import info.bukova.isspst.data.Role;
import info.bukova.isspst.data.User;
import info.bukova.isspst.data.UserSettingsData;
import info.bukova.isspst.security.AuthMethod;
import info.bukova.isspst.services.AbstractService;
import info.bukova.isspst.services.StringXmlMarshaller;
import info.bukova.isspst.services.workgroups.WorkgroupService;
import org.exolab.castor.xml.Marshaller;
import org.exolab.castor.xml.Unmarshaller;
import org.hibernate.LazyInitializationException;
import org.hibernate.Query;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.encoding.PasswordEncoder;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

//import info.bukova.isspst.services.LazyLoader;

public class UserServiceImpl extends AbstractService<User> implements UserService {

	private PasswordEncoder encoder;
	@Autowired
	private RoleService roleService;
	@Autowired
	private WorkgroupService workgroupService;
	private StringXmlMarshaller<UserSettingsData> marshaller;
	private AuthMethod authMethod;
	
	public UserServiceImpl(Marshaller marshaller, Unmarshaller unmarshaller) {
		this.marshaller = new StringXmlMarshaller<UserSettingsData>(marshaller, unmarshaller, UserSettingsData.class);
		authMethod = AuthMethod.DATABASE;
	}
	
	public void setEncoder(PasswordEncoder encoder) {
		this.encoder = encoder;
	}
	
	@Override
	@Transactional
	public UserDetails loadUserByUsername(String username)
			throws UsernameNotFoundException {
		
		Query q = dao.getQuery("from User user join fetch user.authorities where user.username = :username");
		q.setString("username", username);
		User u = (User) q.uniqueResult();
		
		if (u == null)
			throw new UsernameNotFoundException("User " + username + " not found");
		
		return u;
	}

	@Override
	public void setPassword(User user, String password) {
		user.setPassword(encodePassword(user, password));
	}

	@Override
	public boolean hasRole(User user, String authority) {
		for (Role r : user.getAuthorities()) {
			if (r.getAuthority().equals(authority)) {
				return true;
			}
		}
		return false;
	}

	@Override
	@Transactional
	@PreAuthorize("hasPermission(this, 'PERM_EDIT') or hasPermission(#user, this.getUpdateEntityPermission())")
	public void saveWithPwd(User user, String password) {
		if ((password != null) && !password.isEmpty())
		{
			this.setPassword(user, password);
		}
		
		this.update(user);
	}

	@Override
	@Transactional
	public User getCurrent() {
		if (sessionData.getCurrentUser() != null) {
			return sessionData.getCurrentUser();
		}
		
		Authentication auth = SecurityContextHolder.getContext().getAuthentication();
		
		if (auth != null && auth.getPrincipal() != null) {
			try {
				return (User)loadUserByUsername(((UserDetails)auth.getPrincipal()).getUsername());
			} catch(UsernameNotFoundException e) {
				return null;
			} catch (ClassCastException e) {
				throw new AccessDeniedException("Timed out");
			}
		}
		
		return null;
	}

	@Override
	public String encodePassword(User user, String plain) {
		return encoder.encodePassword(plain, user.getUsername());
	}

	@Override
	public void grantAdmin() {
		User tmpAdmin = new User();
		Role tmpRole = new Role();
		tmpRole.setAuthority(Constants.ROLE_ADMIN);
		tmpAdmin.setUsername(Constants.DEF_ADMIN);
		tmpAdmin.addAuthority(tmpRole);
		tmpAdmin.setLastName("fakeLogin");
		SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(tmpAdmin, null, tmpAdmin.getAuthorities()));
	}

	@Override
	public void removeAccess() {
		SecurityContextHolder.getContext().setAuthentication(null);
	}

	@SuppressWarnings("unchecked")
	@Override
	@Transactional
	public List<User> getUsersWithRole(Role r) {
		Set<Role> roles = new HashSet<Role>();
		roles.add(r);
		Query q = dao.getQuery("select u from User u join u.authorities a where a in (:roles)");
		q.setParameterList("roles", roles);
		return q.list();
	}

	@Override
	protected User createEntity() {
		User u = new User();
		Role r = roleService.getRoleByAuthority(Constants.ROLE_USER);
		if (r != null) {
			u.addAuthority(r);
		}
		u.setEnabled(true);
		return u;
	}

	@Override
	@Transactional
//	@LazyLoader({"grid", "form"})
	public void loadAuthorities(User user) {
		try {
			if (user.getAuthorities() == null) {
				throw new LazyInitializationException("");
			}
			
			user.getAuthorities().size();
		} catch (LazyInitializationException ex) {
			User u = dao.getById(user.getId());
			u.getAuthorities().size();
			user.setAuthorities(u.getAuthorities());
		}
	}

	@Override
	public String[] getEmailsForSend(List<User> users) {
		List<String> emails = new ArrayList<String>(); 
		for (User u : users) {
			if (u.getEmail() != null && !u.getEmail().isEmpty() && u.isNotify()) {
				emails.add(u.getEmail());
			}
		}
		
		String[] ret = new String[emails.size()];
		ret = emails.toArray(ret);
		
		return ret;
	}

	@Override
	public UserSettingsData getUserSettings() {
		return getUserSettings(getCurrent());
	}

	@Override
	public UserSettingsData getUserSettings(User user) {
		return marshaller.unmarshalData(user.getSettings());
	}

	@Override
	public void setUserSettings(UserSettingsData settings) {
		getCurrent().setSettings(marshaller.marshalData(settings));		
	}

	@Override
	public String getUpdateEntityPermission() {
		return Constants.PERM_EDIT_OWN;
	}

	@Override
	public void setAuthMethod(AuthMethod method) {
		this.authMethod = method;
	}

	@Override
	public AuthMethod getAuthMethod() {
		return authMethod;
	}

	@Override
	@Transactional
	public void delete(User entity) {
		workgroupService.loadParents(entity);
		
		if (entity.getParents().isEmpty()) {
			super.delete(entity);
		} else {
			throw new DeleteUserException("UserIsInWorkgroup"); 
		}
	}

	@SuppressWarnings("unchecked")
	@Override
	@Transactional
	public List<User> getUsersForCombo() {
		Query q = dao.getQuery("from User u where u.enabled = true order by u.lastName");
		return q.list();
	}

}
