package org.codiki.posts;

import java.io.IOException;
import java.security.Principal;
import java.util.Collections;
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.apache.commons.lang3.StringUtils;
import org.codiki.core.entities.business.SearchEntity;
import org.codiki.core.entities.dto.PostDTO;
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.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class PostService {
	static final int SCORE_TITLE = 50;

	static final int SCORE_DESCRIPTION = 5;

	static final int SCORE_TEXT = 1;

	static final int SCORE_CATEGORY = 30;

	static final int SCORE_AUTHOR_NAME = 40;

	static final int SCORE_AUTHOR_EMAIL = 40;
	
	@Autowired
	private PostRepository postRepository;
	
	@Autowired
	private CategoryRepository categoryRepository;
	
	@Autowired
	private UserService userService;
	
	public Optional<Post> insert(final PostDTO pPost, final HttpServletRequest pRequest,
			final HttpServletResponse pResponse, final Principal pPrincipal) {
		Optional<Post> result = Optional.empty();
		
		final Optional<User> user = userService.getUserByPrincipal(pPrincipal);
		
		if(user.isPresent()) {
			final Post postToSave = new Post(pPost);
			postToSave.setAuthor(user.get());
			postRepository.save(postToSave);
			result = Optional.of(postToSave);
		} else {
			pResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
		}
		
		return result;
	}
	
	public void update(final Post pPost, final HttpServletRequest pRequest,
			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 HttpServletRequest pRequest,
			final HttpServletResponse pResponse, final Principal pPrincipal) throws IOException {
		final Optional<Post> postToDelete = postRepository.getByKey(pPostKey);
		if(postToDelete.isPresent()) {
			final Optional<User> connectedUser = userService.getUserByPrincipal(pPrincipal);
			if(connectedUser.isPresent()) {
				if(connectedUser.get().getKey().equals(postToDelete.get().getAuthor().getKey())) {
					postRepository.delete(postToDelete.get());
				} else {
					pResponse.sendError(HttpServletResponse.SC_FORBIDDEN);
				}
			} else {
				pResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED);
			}
		} else {
			pResponse.sendError(HttpServletResponse.SC_NOT_FOUND);
		}
	}

	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);
		});
		Collections.sort(listSearchEntities, (e1, e2) -> e2.getScore() - e1.getScore());
		
		return listSearchEntities.stream().map(SearchEntity::getPost).collect(Collectors.toList());
	}
	
	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);
		}
	}
	
	String formatForComp(final String pElem) {
		return StringUtils.stripAccents(pElem.toLowerCase());
	}
	
	void calculateScoreForTitle(final SearchEntity pSearchPost, final String pCriteria) {
		if(formatForComp(pSearchPost.getPost().getTitle()).contains(pCriteria)) {
			pSearchPost.increaseScore(SCORE_TITLE);
		}
	}
	
	void calculateScoreForDescription(final SearchEntity pSearchPost, final String pCriteria) {
		final int criteriaOccurence = StringUtils.countMatches(formatForComp(pSearchPost.getPost().getDescription()),
				pCriteria);
		pSearchPost.increaseScore(criteriaOccurence * SCORE_DESCRIPTION);
	}
	
	void calculateScoreForText(final SearchEntity pSearchPost, final String pCriteria) {
		final int criteriaOccurence = StringUtils.countMatches(formatForComp(pSearchPost.getPost().getText()),
				pCriteria);
		pSearchPost.increaseScore(criteriaOccurence * SCORE_TEXT);
	}
	
	void calculateScoreForCategory(final SearchEntity pSearchPost, final String pCriteria) {
		if(formatForComp(pSearchPost.getPost().getCategory().getName()).contains(pCriteria)) {
			pSearchPost.increaseScore(SCORE_CATEGORY);
		}
	}
	
	void calculateScoreForAuthorName(final SearchEntity pSearchPost, final String pCriteria) {
		if(formatForComp(pSearchPost.getPost().getAuthor().getName()).contains(pCriteria)) {
			pSearchPost.increaseScore(SCORE_AUTHOR_NAME);
		}
	}
	
	void calculateScoreForAuthorEmail(final SearchEntity pSearchPost, final String pCriteria) {
		if(formatForComp(pSearchPost.getPost().getAuthor().getEmail()).contains(pCriteria)) {
			pSearchPost.increaseScore(SCORE_AUTHOR_EMAIL);
		}
	}
}
