package com.ciandt.techgallery.service.impl;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

import javax.xml.bind.DatatypeConverter;

import com.ciandt.techgallery.persistence.model.Project;
import org.apache.commons.lang.StringUtils;

import com.ciandt.techgallery.persistence.dao.StorageDAO;
import com.ciandt.techgallery.persistence.dao.TechnologyDAO;
import com.ciandt.techgallery.persistence.dao.impl.TechnologyDAOImpl;
import com.ciandt.techgallery.persistence.dao.storage.StorageDAOImpl;
import com.ciandt.techgallery.persistence.model.TechGalleryUser;
import com.ciandt.techgallery.persistence.model.Technology;
import com.ciandt.techgallery.service.TechnologyService;
import com.ciandt.techgallery.service.UserServiceTG;
import com.ciandt.techgallery.service.enums.RecommendationEnums;
import com.ciandt.techgallery.service.enums.TechnologyOrderOptionEnum;
import com.ciandt.techgallery.service.enums.ValidationMessageEnums;
import com.ciandt.techgallery.service.model.Response;
import com.ciandt.techgallery.service.model.TechnologiesResponse;
import com.ciandt.techgallery.service.model.TechnologyFilter;
import com.google.api.server.spi.response.BadRequestException;
import com.google.api.server.spi.response.InternalServerErrorException;
import com.google.api.server.spi.response.NotFoundException;
import com.google.appengine.api.oauth.OAuthRequestException;
import com.google.appengine.api.users.User;

/**
 * Services for Technology Endpoint requests.
 *
 * @author felipers
 */
public class TechnologyServiceImpl implements TechnologyService {

    /*
     * Attributes --------------------------------------------
     */
    private static TechnologyServiceImpl instance;

    /**
     * tech gallery user service.
     */
    UserServiceTG userService = UserServiceTGImpl.getInstance();
    TechnologyDAO technologyDAO = TechnologyDAOImpl.getInstance();
    StorageDAO storageDAO = StorageDAOImpl.getInstance();

    /*
     * Constructors --------------------------------------------
     */
    private TechnologyServiceImpl() {
    }

    /**
     * Singleton method for the service.
     *
     * @return TechnologyServiceImpl instance.
     * @author <a href="mailto:[email protected]"> João Felipe de Medeiros
     * Moreira </a>
     * @since 07/10/2015
     */
    public static TechnologyServiceImpl getInstance() {
        if (instance == null) {
            instance = new TechnologyServiceImpl();
        }
        return instance;
    }

    /*
     * Methods --------------------------------------------
     */
    @Override
    public Technology addOrUpdateTechnology(Technology technology, User user)
            throws BadRequestException, IOException, GeneralSecurityException {

        Technology foundTechnology = validateInformations(technology);
        Boolean isUpdate = foundTechnology != null && foundTechnology.getId().equals(technology.getId())
                && foundTechnology.getActive().equals(Boolean.TRUE);

        String imageLink = saveImage(technology);

        if (isUpdate) {
            updateTechnology(foundTechnology, technology, user, imageLink);
        } else {
            addTechnology(technology, user, imageLink);
        }

        return technology;
    }

    private void updateTechnology(Technology foundTechnology, Technology technology, User user, String imageLink) {

        foundTechnology.setImage(imageLink);
        foundTechnology.setDescription(technology.getDescription());
        foundTechnology.setShortDescription(technology.getShortDescription());
        foundTechnology.setWebsite(technology.getWebsite());
        foundTechnology.setLastActivity(new Date());
        foundTechnology.setLastActivityUser(getSafeEmail(user));
        foundTechnology.setIdBoard(technology.getIdBoard());
        foundTechnology.setProject(technology.getProject());

        technologyDAO.update(foundTechnology);

    }

    private void addTechnology(Technology technology, User user, String imageLink) {
        technology.setId(technology.convertNameToId(technology.getName()));
        technology.setAuthor(getSafeEmail(user));
        technology.setActive(Boolean.TRUE);
        technology.setCreationDate(new Date());
        technology.setLastActivity(new Date());
        technology.setImage(imageLink);
        technology.initCounters();

        technologyDAO.add(technology);
    }

    private String saveImage(Technology technology) throws IOException, GeneralSecurityException {
        String imageLink = technology.getImage();
        if (technology.getImageContent() != null) {
            imageLink = storageDAO.insertImage(technology.convertNameToId(technology.getName()),
                    new ByteArrayInputStream(DatatypeConverter.parseBase64Binary(technology.getImageContent())));
        }
        return imageLink;
    }

    private String getSafeEmail(User user) {
        if (user != null) {
            return user.getEmail();
        }
        return null;
    }

    /**
     * Method to validade informations of the technology to be added.
     *
     * @param technology to be validated.
     * @throws BadRequestException in case a request with problem were made.
     * @author <a href="mailto:[email protected]"> João Felipe de Medeiros
     * Moreira </a>
     * @since 13/10/2015
     */
    private Technology validateInformations(Technology technology) throws BadRequestException {
        if (StringUtils.isBlank(technology.getId())) {
            throw new BadRequestException(ValidationMessageEnums.TECHNOLOGY_ID_CANNOT_BLANK.message());
        } else if (StringUtils.isBlank(technology.getName())) {
            throw new BadRequestException(ValidationMessageEnums.TECHNOLOGY_NAME_CANNOT_BLANK.message());
        } else if (StringUtils.isBlank(technology.getShortDescription())) {
            throw new BadRequestException(ValidationMessageEnums.TECHNOLOGY_SHORT_DESCRIPTION_BLANK.message());
        } else if (StringUtils.isBlank(technology.getDescription())) {
            throw new BadRequestException(ValidationMessageEnums.TECHNOLOGY_DESCRIPTION_BLANK.message());
        }

        Technology dbTechnology = technologyDAO.findByName(technology.getName());
        if (dbTechnology != null && technology.getId() == null) {
            throw new BadRequestException(ValidationMessageEnums.TECHNOLOGY_NAME_ALREADY_USED.message());
        }
        if (technology.getId() != null && dbTechnology != null && !dbTechnology.getName().equals(technology.getName())) {
            throw new BadRequestException(ValidationMessageEnums.TECHNOLOGY_NAME_CANNOT_CHANGE.message());
        }
        return dbTechnology;
    }

    /**
     * GET for getting all technologies.
     *
     * @throws NotFoundException .
     */
    @Override
    public Response getTechnologies(User user)
            throws InternalServerErrorException, NotFoundException, BadRequestException {
        List<Technology> techEntities = technologyDAO.findAllActives();
        // if list is null, return a not found exception
        if (techEntities == null || techEntities.isEmpty()) {
            return new TechnologiesResponse();
        } else {
            verifyTechnologyFollowedByUser(user, techEntities);
            TechnologiesResponse response = new TechnologiesResponse();
            Technology.sortTechnologiesDefault(techEntities);
            response.setTechnologies(techEntities);
            return response;
        }
    }

    private List<Technology> setDateFilteredList(List<Technology> completeList, Date dateReference) {
        List<Technology> dateFilteredList = new ArrayList<>();
        for (Technology technology : completeList) {
            if (technology.getLastActivity().after(dateReference) || technology.getLastActivity().equals(dateReference)) {
                dateFilteredList.add(technology);
            }
        }
        return dateFilteredList;
    }

    private Date setDateReference(Date currentDate, int daysToSubtract) {
        Calendar cal = Calendar.getInstance();
        cal.setTime(currentDate);
        cal.add(Calendar.DATE, daysToSubtract);
        Date dateReference = cal.getTime();
        return dateReference;
    }

    @Override
    public Response findTechnologiesByFilter(TechnologyFilter techFilter, User user)
            throws InternalServerErrorException, NotFoundException, BadRequestException {

        validateUser(user);
        TechGalleryUser techUser = userService.getUserByEmail(user.getEmail());
        if (techFilter.getRecommendationIs() != null
                && techFilter.getRecommendationIs().equals(RecommendationEnums.UNINFORMED.message())) {
            techFilter.setRecommendationIs("");
        }
        List<Technology> completeList = technologyDAO.findAllActives();
        completeList = filterByLastActivityDate(techFilter, completeList);

        List<Technology> filteredList = new ArrayList<>();
        if (StringUtils.isBlank(techFilter.getTitleContains()) && techFilter.getRecommendationIs() == null) {
            filteredList.addAll(completeList);
        } else {
            verifyFilters(techFilter, completeList, filteredList, techUser.getProject());
        }

        if (filteredList.isEmpty()) {
            return new TechnologiesResponse();
        } else {
            if (techFilter.getOrderOptionIs() != null && !techFilter.getOrderOptionIs().isEmpty()) {
                if(techFilter.getOrderOptionIs().equals("Projeto")){
                    filteredList = Technology.sortTechnologies(filteredList, techUser.getProject());
                } else {
                    filteredList = Technology.sortTechnologies(filteredList,
                            TechnologyOrderOptionEnum.fromString(techFilter.getOrderOptionIs()));
                }
            } else {
                Technology.sortTechnologiesDefault(filteredList);
            }
            TechnologiesResponse response = new TechnologiesResponse();
            response.setTechnologies(filteredList);
            return response;
        }
    }

    private List<Technology> filterByLastActivityDate(TechnologyFilter techFilter, List<Technology> completeList) {
        List<Technology> dateFilteredList = new ArrayList<>();
        if (techFilter.getDateFilter() != null) {
            Date currentDate = new Date();
            switch (techFilter.getDateFilter()) {
                case LAST_DAY:
                    Date lastDay = setDateReference(currentDate, -1);
                    dateFilteredList = setDateFilteredList(completeList, lastDay);
                    break;

                case LAST_7_DAYS:
                    Date last7Days = setDateReference(currentDate, -7);
                    dateFilteredList = setDateFilteredList(completeList, last7Days);
                    break;

                case LAST_30_DAYS:
                    Date last30Days = setDateReference(currentDate, -30);
                    dateFilteredList = setDateFilteredList(completeList, last30Days);
                    break;
                default:
                    break;
            }
            completeList = dateFilteredList;
        }
        return completeList;
    }

    private void verifyTechnologyFollowedByUser(User user, List<Technology> filteredList)
            throws NotFoundException, BadRequestException, InternalServerErrorException {
        TechGalleryUser techUser = userService.getUserByGoogleId(user.getUserId());
        if (techUser != null && techUser.getFollowedTechnologyIds() != null && !techUser.getFollowedTechnologyIds().isEmpty()) {
            for (Technology technology : filteredList) {
                if (techUser.getFollowedTechnologyIds().contains(technology.getId())) {
                    technology.setFollowedByUser(true);
                }
            }
        }
    }

    private void verifyFilters(TechnologyFilter techFilter, List<Technology> completeList,
                               List<Technology> filteredList, Project project) {
        for (Technology technology : completeList) {
            if (verifyTitleAndShortDescriptionFilter(techFilter, technology)) {
                if (techFilter.getRecommendationIs() != null) {
                    if (verifyRecommendationFilter(techFilter, technology, project)) {
                        filteredList.add(technology);
                    } else {
                        continue;
                    }
                } else {
                    filteredList.add(technology);
                    continue;
                }
            } else if (verifyRecommendationFilter(techFilter, technology, project) && techFilter.getTitleContains() == null) {
                filteredList.add(technology);
                continue;
            }
        }
    }

    private boolean verifyRecommendationFilter(TechnologyFilter techFilter, Technology technology, Project project) {
        if (technology.getRecommendation() == null && techFilter.getRecommendationIs() == "") {
            return true;
        } else if (
                technology.getRecommendation() != null && techFilter.getRecommendationIs() != null
                        && (technology.getRecommendation().toLowerCase().equals(techFilter.getRecommendationIs().toLowerCase())
                        || techFilter.getRecommendationIs().toLowerCase().equals(RecommendationEnums.ANY.message().toLowerCase()))
                        || verifyProjectSpecificFilter(techFilter, technology, project)
        ) {
            return true;
        }
        return false;
    }

    private boolean verifyTitleAndShortDescriptionFilter(TechnologyFilter techFilter, Technology technology) {
        if (techFilter.getTitleContains() != null
                && (technology.getName().toLowerCase().contains(techFilter.getTitleContains().toLowerCase()) || technology
                .getShortDescription().toLowerCase().contains(techFilter.getShortDescriptionContains().toLowerCase()))) {
            return true;
        }
        return false;
    }

    private boolean verifyProjectSpecificFilter(TechnologyFilter techFilter, Technology technology, Project project) {
        if (techFilter.getRecommendationIs() != null && technology.getProject() != null && project != null &&
                techFilter.getRecommendationIs().toLowerCase().equals(RecommendationEnums.PROJECT_SPECIFIC.message().toLowerCase()) &&
                Long.compare(technology.getProject().getId(), project.getId()) == 0) {
            return true;
        }
        return false;
    }

    @Override
    public Technology getTechnologyById(String id, User user)
            throws NotFoundException, BadRequestException, InternalServerErrorException {
        Technology tech = technologyDAO.findByIdActive(id);
        if (tech == null) {
            throw new NotFoundException(ValidationMessageEnums.TECHNOLOGY_NOT_EXIST.message());
        } else {
            if (user != null) {
                TechGalleryUser techUser = userService.getUserByGoogleId(user.getUserId());
                if (techUser != null && techUser.getFollowedTechnologyIds() != null && techUser.getFollowedTechnologyIds().contains(tech.getId())) {
                    tech.setFollowedByUser(true);
                }
            }
            return tech;
        }
    }

    /**
     * Validate the user logged in.
     *
     * @param user info about user from google
     * @throws InternalServerErrorException in case something goes wrong
     * @throws NotFoundException            in case the information are not founded
     * @throws BadRequestException          in case a request with problem were made.
     */
    private void validateUser(User user) throws BadRequestException, NotFoundException, InternalServerErrorException {

        if (user == null || user.getUserId() == null || user.getUserId().isEmpty()) {
            throw new BadRequestException(ValidationMessageEnums.USER_GOOGLE_ENDPOINT_NULL.message());
        }

        TechGalleryUser techUser = userService.getUserByGoogleId(user.getUserId());
        if (techUser == null) {
            throw new NotFoundException(ValidationMessageEnums.USER_NOT_EXIST.message());
        }
    }

    @Override
    public List<String> getOrderOptions(User user) {
        List<String> orderOptions = new ArrayList<String>();
        for (TechnologyOrderOptionEnum item : TechnologyOrderOptionEnum.values()) {
            orderOptions.add(item.option());
        }
        return orderOptions;
    }

    @Override
    public void addCommentariesCounter(Technology entity) {
        if (entity != null) {
            entity.addCommentariesCounter();
        }
        technologyDAO.update(entity);
    }

    @Override
    public void removeCommentariesCounter(Technology entity) {
        if (entity != null) {
            entity.removeCommentariesCounter();
        }
        technologyDAO.update(entity);

    }

    @Override
    public void addRecomendationCounter(Technology entity, Boolean score) {
        if (entity == null) {
            return;
        }
        if (score) {
            entity.addPositiveRecommendationsCounter();
        } else {
            entity.addNegativeRecommendationsCounter();
        }
        technologyDAO.update(entity);
    }

    @Override
    public void removeRecomendationCounter(Technology entity, Boolean score) {
        if (entity == null) {
            return;
        }
        if (score) {
            entity.removePositiveRecommendationsCounter();
        } else {
            entity.removeNegativeRecommendationsCounter();
        }
        technologyDAO.update(entity);
    }

    @Override
    public void updateEdorsedsCounter(Technology technology, Integer size) {
        technology.setEndorsersCounter(size);
        technologyDAO.update(technology);
    }

    @Override
    public void audit(String technologyId, User user)
            throws NotFoundException, BadRequestException, InternalServerErrorException {
        Technology technology = getTechnologyById(technologyId, user);
        technology.setLastActivity(new Date());
        technology.setLastActivityUser(user.getEmail());
        technologyDAO.update(technology);
    }

    @Override
    public Technology deleteTechnology(String technologyId, User user)
            throws InternalServerErrorException, BadRequestException, NotFoundException, OAuthRequestException {
        validateUser(user);
        Technology technology = technologyDAO.findById(technologyId);
        if (technology == null) {
            throw new NotFoundException(ValidationMessageEnums.NO_TECHNOLOGY_WAS_FOUND.message());
        }
        technology.setActive(Boolean.FALSE);
        technology.setLastActivity(new Date());
        technology.setLastActivityUser(user.getEmail());
        technologyDAO.update(technology);
        return technology;
    }
}