package org.codiki.posts;

import org.apache.commons.lang3.StringUtils;
import org.codiki.core.entities.business.SearchEntity;
import org.codiki.core.entities.persistence.Category;
import org.codiki.core.entities.persistence.Post;
import org.codiki.core.entities.persistence.User;
import org.codiki.core.repositories.CategoryRepository;
import org.codiki.core.repositories.PostRepository;
import org.codiki.core.services.IllegalAccessException;
import org.codiki.core.services.UserService;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.security.Principal;
import java.util.*;
import java.util.stream.Collectors;

@Service
public class PostService {
	private static final int SCORE_TITLE = 50;
	private static final int SCORE_DESCRIPTION = 5;
	private static final int SCORE_TEXT = 1;
	private static final int SCORE_CATEGORY = 30;
	private static final int SCORE_AUTHOR_NAME = 40;
	private static final int SCORE_AUTHOR_EMAIL = 40;
	
	private final PostRepository postRepository;

	private final CategoryRepository categoryRepository;

	private final UserService userService;

	/** Constructor. */
	public PostService(PostRepository postRepository,
					   CategoryRepository categoryRepository,
					   UserService userService) {
		this.postRepository = postRepository;
		this.categoryRepository = categoryRepository;
		this.userService = userService;
	}

	public int insert(final Post pPost, final Principal pPrincipal) {
		return userService.getUserByPrincipal(pPrincipal)
				.map(user -> {
					pPost.setCreationDate(new Date());
					pPost.setAuthor(user);
					postRepository.save(pPost);
					return HttpServletResponse.SC_OK;
				})
				.orElse(HttpServletResponse.SC_UNAUTHORIZED);
	}
	
	public void update(final Post pPost, final HttpServletResponse pResponse, final Principal pPrincipal)
			throws IOException {
		final Optional<User> connectedUser = userService.getUserByPrincipal(pPrincipal);
		
		if(connectedUser.isPresent() && connectedUser.get().getKey().equals(pPost.getAuthor().getKey())) {
			final Optional<Post> postOpt = postRepository.getByKey(pPost.getKey());
			
			if(postOpt.isPresent()) {
				final Optional<Category> category = categoryRepository.findById(pPost.getCategory().getId());
				if(category.isPresent()) {
					final Post post = postOpt.get();
					
					post.setTitle(pPost.getTitle());
					post.setImage(pPost.getImage());
					post.setDescription(pPost.getDescription());
					post.setCreationDate(new Date());
					post.setText(pPost.getText());
					post.setCategory(category.get());
					
					postRepository.save(post);
				} else {
					pResponse.sendError(HttpServletResponse.SC_BAD_REQUEST);
				}
			} else {
				pResponse.sendError(HttpServletResponse.SC_NOT_FOUND);
			}
		} else {
			pResponse.sendError(HttpServletResponse.SC_FORBIDDEN);
		}
	}
	
	public void delete(final String pPostKey, final Principal pPrincipal){
		String userKey = userService.getUserByPrincipal(pPrincipal)
				.map(User::getKey)
				.orElseThrow(NoSuchElementException::new);

		Post postToDelete = postRepository.getByKey(pPostKey)
				.orElseThrow(NoSuchElementException::new);

		if(postToDelete.getAuthor() == null || !postToDelete.getAuthor().getKey().equals(userKey)) {
			throw new IllegalAccessException();
		}

		postRepository.delete(postToDelete);
	}

	public List<Post> search(String pSearchCriteria) {
		final String[] criteriasArray = pSearchCriteria.split(" ");
		
		final List<SearchEntity> listSearchEntities = new LinkedList<>();
		postRepository.search(criteriasArray).stream().map(SearchEntity::new).forEach(e -> { 
			calculateScore(e, criteriasArray);
			listSearchEntities.add(e);
		});
		listSearchEntities.sort((e1, e2) -> e2.getScore() - e1.getScore());
		
		return listSearchEntities.stream().map(SearchEntity::getPost).collect(Collectors.toList());
	}
	
	private void calculateScore(final SearchEntity searchPost, final String[] pCriteriasArray) {
		for(final String criteria : pCriteriasArray) {
			String formattedCriteria = formatForComp(criteria);
			
			calculateScoreForTitle(searchPost, formattedCriteria);
			calculateScoreForDescription(searchPost, formattedCriteria);
			calculateScoreForText(searchPost, formattedCriteria);
			calculateScoreForCategory(searchPost, formattedCriteria);
			calculateScoreForAuthorName(searchPost, formattedCriteria);
			calculateScoreForAuthorEmail(searchPost, formattedCriteria);
		}
	}
	
	private String formatForComp(final String pElem) {
		return StringUtils.stripAccents(pElem.toLowerCase());
	}

	private void calculateScoreForTitle(final SearchEntity pSearchPost, final String pCriteria) {
		if(formatForComp(pSearchPost.getPost().getTitle()).contains(pCriteria)) {
			pSearchPost.increaseScore(SCORE_TITLE);
		}
	}

	private void calculateScoreForDescription(final SearchEntity pSearchPost, final String pCriteria) {
		final int criteriaOccurence = StringUtils.countMatches(formatForComp(pSearchPost.getPost().getDescription()),
				pCriteria);
		pSearchPost.increaseScore(criteriaOccurence * SCORE_DESCRIPTION);
	}

	private void calculateScoreForText(final SearchEntity pSearchPost, final String pCriteria) {
		final int criteriaOccurence = StringUtils.countMatches(formatForComp(pSearchPost.getPost().getText()),
				pCriteria);
		pSearchPost.increaseScore(criteriaOccurence * SCORE_TEXT);
	}

	private void calculateScoreForCategory(final SearchEntity pSearchPost, final String pCriteria) {
		if(formatForComp(pSearchPost.getPost().getCategory().getName()).contains(pCriteria)) {
			pSearchPost.increaseScore(SCORE_CATEGORY);
		}
	}

	private void calculateScoreForAuthorName(final SearchEntity pSearchPost, final String pCriteria) {
		if(formatForComp(pSearchPost.getPost().getAuthor().getName()).contains(pCriteria)) {
			pSearchPost.increaseScore(SCORE_AUTHOR_NAME);
		}
	}

	private void calculateScoreForAuthorEmail(final SearchEntity pSearchPost, final String pCriteria) {
		if(formatForComp(pSearchPost.getPost().getAuthor().getEmail()).contains(pCriteria)) {
			pSearchPost.increaseScore(SCORE_AUTHOR_EMAIL);
		}
	}
}
