package org.codiki.account;

import java.io.IOException;
import java.nio.file.NoSuchFileException;
import java.security.Principal;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.codiki.core.entities.dto.ImageDTO;
import org.codiki.core.entities.dto.PasswordWrapperDTO;
import org.codiki.core.entities.dto.UserDTO;
import org.codiki.core.entities.persistence.User;
import org.codiki.core.repositories.ImageRepository;
import org.codiki.core.repositories.UserRepository;
import org.codiki.core.security.CustomAuthenticationProvider;
import org.codiki.core.services.FileUploadService;
import org.codiki.core.services.UserService;
import org.codiki.core.utils.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

@Service
public class AccountService {
	/** Logger. */
	private static final Logger LOG = LoggerFactory.getLogger(FileUploadService.class);

	private CustomAuthenticationProvider authenticationProvider;
	
	private UserService userService;
	
	private UserRepository userRepository;

	private FileUploadService fileUploadService;
	
	private ImageRepository imageRepository;

	/**
	 * Constructor.
	 * @param authenticationProvider
	 * @param userService
	 * @param userRepository
	 * @param fileUploadService
	 * @param imageRepository
	 */
	public AccountService(CustomAuthenticationProvider authenticationProvider,
						  UserService userService,
						  UserRepository userRepository,
						  FileUploadService fileUploadService,
						  ImageRepository imageRepository) {
		this.authenticationProvider = authenticationProvider;
		this.userService = userService;
		this.userRepository = userRepository;
		this.fileUploadService = fileUploadService;
		this.imageRepository = imageRepository;
	}

	public User authenticate(final User pUser) throws BadCredentialsException {
		final User user = userService.checkCredentials(pUser.getEmail(), pUser.getPassword());
		
		authenticationProvider.authenticate(new UsernamePasswordAuthenticationToken(user.getEmail(), user.getPassword()));
		
		return user;
	}
	
	public int changePassword(final User pUser, final PasswordWrapperDTO pPasswordWrapper) throws IllegalArgumentException {
		int resultCode = HttpServletResponse.SC_NO_CONTENT;

		if(pPasswordWrapper.getNewPassword().equals(pPasswordWrapper.getConfirmPassword())) {
			// We fetch the connected user from database to get his hashed password
			User user = userRepository.findById(pUser.getId())
					.filter(u -> StringUtils.compareHash(pPasswordWrapper.getOldPassword(),
							u.getPassword()))
					.orElseThrow(IllegalArgumentException::new);

			user.setPassword(StringUtils.hashPassword(pPasswordWrapper.getNewPassword()));
			userRepository.save(user);
		} else {
			resultCode = HttpServletResponse.SC_BAD_REQUEST;
		}

		return resultCode;
	}
	
	public String uploadFile(final MultipartFile pFile, final HttpServletRequest pRequest,
			final HttpServletResponse pResponse, final Principal pPrincipal) throws IOException {
		final String avatarFileName = fileUploadService.uploadProfileImage(pFile);
		
		final Optional<User> connectedUser = userService.getUserByPrincipal(pPrincipal);
		if(connectedUser.isPresent()) {
			final Optional<User> userFromDb = userRepository.findById(connectedUser.get().getId());
			if(userFromDb.isPresent()) {
				userFromDb.get().setImage(avatarFileName);
				userRepository.save(userFromDb.get());
			} else {
				pResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED);
			}
		} else {
			pResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED);
		}
		
		return avatarFileName;
	}

	public Optional<Resource> loadAvatar(final String pAvatarFileName) {
		return fileUploadService.loadAvatar(pAvatarFileName);
	}
	
	public List<ImageDTO> getUserImages(final HttpServletRequest pRequest, final HttpServletResponse pResponse,
			final Principal pPrincipal) throws IOException {
		List<ImageDTO> result = new LinkedList<>();
		
		final Optional<User> connectedUser = userService.getUserByPrincipal(pPrincipal);
		if(connectedUser.isPresent()) {
			result = imageRepository.getByUserId(connectedUser.get().getId())
					.stream().map(ImageDTO::new).collect(Collectors.toList());
		} else {
			pResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED);
		}
		
		return result;
	}

	public void signin(final User pUser, final HttpServletResponse pResponse) throws IOException {
		if(pUser.getName() == null || pUser.getEmail() == null || pUser.getPassword() == null || "".equals(pUser.getPassword().trim())) {
			pResponse.sendError(HttpServletResponse.SC_BAD_REQUEST);
		} else if(userRepository.findByEmail(pUser.getEmail()).isPresent()) {
			pResponse.sendError(HttpServletResponse.SC_CONFLICT);
		} else {
			User user = new User();

			user.setName(pUser.getName());
			user.setEmail(pUser.getEmail());
			user.setPassword(StringUtils.hashPassword(pUser.getPassword()));
			user.setInscriptionDate(new Date());
			userRepository.save(user);
		}
	}
	
	public void updateUser(final UserDTO pUser, final HttpServletRequest pRequest,
			final HttpServletResponse pResponse, final Principal pPrincipal) throws IOException {
		final Optional<User> connectedUserOpt = userService.getUserByPrincipal(pPrincipal);
		if(connectedUserOpt.isPresent()) {
			final User connectedUser = connectedUserOpt.get();
			
			final Optional<User> userFromDb = userRepository.findByEmail(pUser.getEmail());
			
			/*
			 * If a user is returned by the repository, that's the email adress is used, but
			 * if it is not the same as the connected user, that's the email adress
			 * corresponds to another user. So a 409 error is sent. Otherwise, if no user is
			 * returned by the repository, that's the email adress is free to be used. So,
			 * user can change him email adress.
			 */
			if(userFromDb.isPresent() && !connectedUser.getEmail().equals(userFromDb.get().getEmail())) {
				pResponse.sendError(HttpServletResponse.SC_CONFLICT);
			} else {
				connectedUser.setName(pUser.getName());
				connectedUser.setEmail(pUser.getEmail());
				
				userRepository.save(connectedUser);
			}
		} else {
			pResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED);
		}
	}
}
