package org.c4sg.service.impl;


import org.c4sg.constant.Constants;
import org.c4sg.dao.OrganizationDAO;
import org.c4sg.dao.UserDAO;
import org.c4sg.dao.UserOrganizationDAO;
import org.c4sg.dto.CreateOrganizationDTO;
import org.c4sg.dto.OrganizationDTO;
import org.c4sg.dto.ProjectDTO;
import org.c4sg.entity.Organization;
import org.c4sg.entity.User;
import org.c4sg.entity.UserOrganization;
import org.c4sg.exception.UserOrganizationException;
import org.c4sg.mapper.OrganizationMapper;
import org.c4sg.service.AsyncEmailService;
import org.c4sg.service.GeocodeService;
import org.c4sg.service.OrganizationService;
import org.c4sg.service.ProjectService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static java.util.Objects.nonNull;
import static java.util.Objects.requireNonNull;

import java.math.BigDecimal;

@Service
public class OrganizationServiceImpl implements OrganizationService {

    @Autowired
    private OrganizationDAO organizationDAO;

    @Autowired
    private OrganizationMapper organizationMapper;

	@Autowired
	private UserDAO userDAO;

	@Autowired
	private UserOrganizationDAO userOrganizationDAO;
	
	@Autowired
	private ProjectService projectService;
	
    @Autowired
    private AsyncEmailService asyncEmailService;
    
    @Autowired
	private GeocodeService geocodeService;

    public void save(OrganizationDTO organizationDTO) {
        Organization organization = organizationMapper.getOrganizationEntityFromDto(organizationDTO);
        organizationDAO.save(organization);
    }

    public List<OrganizationDTO> findOrganizations() {
        List<Organization> organizations = organizationDAO.findAllByOrderByIdDesc();
        List<OrganizationDTO> organizationDTOS = organizations.stream().map(o -> organizationMapper.getOrganizationDtoFromEntity(o)).collect(Collectors.toList());
        return organizationDTOS;
    }

    public OrganizationDTO findById(int id) {
        return organizationMapper.getOrganizationDtoFromEntity(organizationDAO.findOne(id));
    }

    public List<OrganizationDTO> findByKeyword(String keyWord) {
        List<Organization> organizations = organizationDAO.findByNameOrDescription(keyWord, keyWord);

        return organizations.stream().map(o -> organizationMapper.getOrganizationDtoFromEntity(o)).collect(Collectors.toList());
    }
    
    public Page<OrganizationDTO> findByCriteria(String keyWord, List<String> countries, Boolean open, String status, List<String> categories, Integer page, Integer size) {
        Page<Organization> organizationPages;
        List<Organization> organizations = null;

        if (page == null) page = 0;

        if (size == null) {
            if (countries == null || countries.isEmpty()) {
                if (open == null) {
                    if (categories != null) {
                        organizations = organizationDAO.findByCriteria(keyWord, open, status, categories);
                    } else {
                        organizations = organizationDAO.findByCriteriaNoCategoriesFilter(keyWord, open, status);
	    			}
                } else {
                    if (categories == null) {
                        organizations = organizationDAO.findByCriteriaAndOpenNoCategoriesFilter(keyWord, open, status);
                    } else {
	    			organizations = organizationDAO.findByCriteriaAndOpen(keyWord, open, status, categories);
	    		}
                }
            } else {
                if (open == null) {
                    if (categories != null) {
                        organizations = organizationDAO.findByCriteriaAndCountries(keyWord, countries, open, status, categories);
                    }
                } else {
                    organizations = organizationDAO.findByCriteriaAndCountriesAndOpen(keyWord, countries, open, status, categories);
	    			}
	    		}    		
            organizationPages = new PageImpl<>(organizations);

            return organizationPages.map(o -> organizationMapper.getOrganizationDtoFromEntity(o));
	    	}

        Pageable pageable = new PageRequest(page, size);

        if (countries == null || countries.isEmpty()) {
            if (open == null) {
                if (categories == null) {
                    organizationPages = organizationDAO.findByCriteriaNoCategoriesFilter(keyWord, open, status, pageable);
    	} else {
                    organizationPages = organizationDAO.findByCriteria(keyWord, open, status, categories, pageable);
                }
            } else {
                if (categories == null) {
                    organizationPages = organizationDAO.findByCriteriaAndOpenNoCategoriesFilter(keyWord, open, status, pageable);
                } else {
                    organizationPages = organizationDAO.findByCriteriaAndOpen(keyWord, open, status, categories, pageable);
	    			}
	    		}    		
        } else if (open == null) {
            organizationPages = organizationDAO.findByCriteriaAndCountries(keyWord, countries, open, status, categories, pageable);
        } else {
            organizationPages = organizationDAO.findByCriteriaAndCountriesAndOpen(keyWord, countries, open, status, categories, pageable);
	    	}

    	return organizationPages.map(o -> organizationMapper.getOrganizationDtoFromEntity(o));    	
    }
      
    public OrganizationDTO createOrganization(CreateOrganizationDTO createOrganizationDTO) {
        Organization organization = organizationDAO.save(organizationMapper.getOrganEntityFromCreateOrganDto(createOrganizationDTO));
        return organizationMapper.getOrganizationDtoFromEntity(organization);
    }

    public OrganizationDTO updateOrganization(int id, OrganizationDTO organizationDTO) {
    	
        Organization organization = organizationDAO.findOne(id);

        if (organization == null) {
        	System.out.println("Organization does not exist.");
        } else {
        	organization = organizationMapper.getOrganizationEntityFromDto(organizationDTO);
            try {
    			Map<String, BigDecimal> geoCode = geocodeService.getGeoCode(organization.getState(), organization.getCountry());
    			organization.setLatitude(geoCode.get("lat"));
    			organization.setLongitude(geoCode.get("lng"));
            } catch (Exception e) {
            	//throw new NotFoundException("Error getting geocode");
            	System.out.println("Error getting geocode: " + e.toString());
    		}
            organizationDAO.save(organization);
            String newStatus = organization.getStatus();
            
            // Notify admin users of new organization, or update for a declined organization
            if (newStatus.equals(Constants.ORGANIZATION_STATUS_PENDIONG_REVIEW) || newStatus.equals(Constants.ORGANIZATION_STATUS_DECLINED)) {
            	
            	String toAddress = null;
            	List<User> users = userDAO.findByKeyword(null, "A", "A", null);
            	if (users != null && !users.isEmpty()) {
            		User adminUser = users.get(0);
            		toAddress = adminUser.getEmail();
            	}	
            			
            	Map<String, Object> context = new HashMap<String, Object>();
            	context.put("organization", organization);         	
            	asyncEmailService.sendWithContext(Constants.C4SG_ADDRESS, toAddress, "", Constants.SUBJECT_NEW_ORGANIZATION_REVIEW, Constants.TEMPLATE_NEW_ORGANIZATION_REVIEW, context);
            	System.out.println("New organization email sent: Organization=" + organization.getId() + " ; Email=" + toAddress);
            }
        }
        
        return organizationMapper.getOrganizationDtoFromEntity(organization);
    }

    public void deleteOrganization(int id) {
    	Organization organization = organizationDAO.findOne(id);
        if (organization != null) {
    		organization.setStatus(Constants.ORGANIZATION_STATUS_DELETED);
    		// TODO Delete logo from S3 by frontend
    		organizationDAO.save(organization);
            List<ProjectDTO> projects = projectService.findByOrganization(id, null);
            for (ProjectDTO project : projects) {
    			projectService.deleteProject(project.getId());
    		}
    		organizationDAO.deleteUserOrganizations(id);
    		//TODO: Local or Timezone?
    		//TODO: Format date
    		//organization.setDeleteTime(LocalDateTime.now().toString());
    		//organization.setDeleteBy(user.getUsername());
    	}
    }

    @Override
    public List<OrganizationDTO> findByUser(Integer userId) {
      User user = userDAO.findById(userId);
      requireNonNull(user, "Invalid User Id");
      List<UserOrganization> userOrganizations = userOrganizationDAO.findByUserId(userId);
      List<OrganizationDTO> organizationDtos = new ArrayList<OrganizationDTO>();
      for (UserOrganization userOrganization : userOrganizations) {
        organizationDtos.add(organizationMapper.getOrganizationDtoFromEntity(userOrganization));
      }
      return organizationDtos;
    }
    
    @Override
    public OrganizationDTO saveUserOrganization(Integer userId, Integer organizationId) throws UserOrganizationException {
        User user = userDAO.findById(userId);
        requireNonNull(user, "Invalid User Id");
        Organization organization = organizationDAO.findOne(organizationId);
        requireNonNull(organization, "Invalid organization Id");
        UserOrganization userOrganization = userOrganizationDAO.findByUser_IdAndOrganization_Id(userId, organizationId);
        if (nonNull(userOrganization)) {
            throw new UserOrganizationException("The user organization relationship already exists.");
        } else {
        	userOrganization = new UserOrganization();
        	userOrganization.setUser(user);
        	userOrganization.setOrganization(organization);
        	userOrganizationDAO.save(userOrganization);
        }
        
        return organizationMapper.getOrganizationDtoFromEntity(organization);
    }
    
	@Override
	public void saveLogo(Integer id, String imgUrl) {
		organizationDAO.updateLogo(imgUrl, id);
	}
	
	@Override
	public void approveOrDecline(Integer id, String status) {
		organizationDAO.approveOrDecline(id, status);
		
        // Notify organization user of approval or deny
       	String toAddress = null;
    	List<User> users = userDAO.findByOrgId(id);
    	if (users != null && !users.isEmpty()) {
    		User orgUser = users.get(0);
    		toAddress = orgUser.getEmail();
    	}	
        			
       	Map<String, Object> context = new HashMap<String, Object>();     	
        if (status.equals(Constants.ORGANIZATION_STATUS_ACTIVE)) asyncEmailService
                .sendWithContext(Constants.C4SG_ADDRESS, toAddress, "", Constants.SUBJECT_NEW_ORGANIZATION_APPROVE, Constants.TEMPLATE_NEW_ORGANIZATION_APPROVE, context);
        else if (status.equals(Constants.ORGANIZATION_STATUS_DECLINED)) asyncEmailService
                .sendWithContext(Constants.C4SG_ADDRESS, toAddress, "", Constants.SUBJECT_NEW_ORGANIZATION_DECLINE, Constants.TEMPLATE_NEW_ORGANIZATION_DECLINE, context);
       	System.out.println("Organization approval/decline email sent: Organization=" + id + " ; Email=" + toAddress);
	}

	@Override
	public int countByCountry() {
		return organizationDAO.countByCountry();
	}
}