/*
 *
 *  * Copyright (C) 2003-2016 eXo Platform SAS.
 *  *
 *  * This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Affero General Public License
 *  as published by the Free Software Foundation; either version 3
 *  of the License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, see<http://www.gnu.org/licenses/>.
 *
 */
package org.exoplatform.cs.service;

import org.apache.commons.lang3.StringUtils;
import org.exoplatform.commons.utils.ListAccess;
import org.exoplatform.cs.dao.SpaceDAO;
import org.exoplatform.cs.dto.BasicEntityDTO;
import org.exoplatform.cs.dto.IssueType;
import org.exoplatform.cs.dto.SpaceDTO;
import org.exoplatform.cs.dto.TicketDTO;
import org.exoplatform.cs.entity.SpaceEntity;
import org.exoplatform.cs.exception.EntityAlreadyExistsException;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;
import org.exoplatform.services.organization.Group;
import org.exoplatform.services.organization.Membership;
import org.exoplatform.services.organization.OrganizationService;
import org.exoplatform.social.core.space.SpaceUtils;
import org.exoplatform.social.core.space.model.Space;
import org.exoplatform.social.core.space.spi.SpaceService;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * Created by The eXo Platform SAS
 * 
 * @author boubaker.khanfir@exoplatform.com
 * @since Apr 27, 2016
 */
public class CSSpaceService extends BaseService {
  private static final Log LOG = ExoLogger.getLogger(CSSpaceService.class);
  public static final java.lang.String CUSTOMERS_GROUP = "exo.cs.customers.group";

  private CSHandlerStarter    csHandlerStarter;

  private SpaceService        spaceService;

  private OrganizationService organizationService;

  private TicketService       ticketService;

  private static final DateFormat   DEFAULT_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");



  public CSSpaceService(SpaceDAO spaceDAO,
                        SpaceService spaceService,
                        CSHandlerStarter csHandlerStarter,
                        OrganizationService organizationService,
                        TicketService ticketService) {
    super(null, spaceDAO, null, null);
    this.spaceService = spaceService;
    this.organizationService = organizationService;
    this.csHandlerStarter = csHandlerStarter;
    this.ticketService = ticketService;
  }

  public SpaceDTO getSpace(String groupId) throws Exception {
    SpaceEntity entity = getSpaceEntityByGroupId(groupId);
    if (entity == null) {
      return null;
    } else {
      return convertToDTO(entity);
    }
  }

  private SpaceEntity getSpaceEntityByGroupId(String groupId) {
    if (StringUtils.isBlank(groupId)) {
      throw new IllegalArgumentException("Method getSpace - Parameter 'groupId' is null");
    }
    if (!groupId.startsWith(SpaceUtils.SPACE_GROUP)) {
      throw new IllegalArgumentException("Method getSpace - Parameter 'groupId' format is not know, it should start with: "
          + SpaceUtils.SPACE_GROUP);
    }
    SpaceEntity entity = null;
    try {
      entity = spaceDAO.find(groupId);
    } catch (Exception e) {
      LOG.debug("Error while getting space with groupId {}", groupId);
    }
    return entity;
  }

  public void removeSpace(String groupId) throws Exception {
    SpaceEntity entity = getSpaceEntityByGroupId(groupId);
    if (entity != null) {
      spaceDAO.delete(entity);
    } else {
      LOG.warn("Cannot delete cs space entity with group id '{}' because it wasn't found", groupId);
    }
  }

  public List<SpaceDTO> getAllSpaces() {
    List<SpaceEntity> entities = spaceDAO.findAll();
    List<SpaceDTO> dtos = new ArrayList<SpaceDTO>();
    for (SpaceEntity entity : entities) {
      SpaceDTO space = convertToDTO(entity);
      if(space != null) {
        dtos.add(space);
      }
    }
    return dtos;
  }


  public List<SpaceDTO> getActiveSpaces() {
    List<SpaceEntity> entities = spaceDAO.getActiveSpaces(0,0);
    List<SpaceDTO> dtos = new ArrayList<SpaceDTO>();
    for (SpaceEntity entity : entities) {
      SpaceDTO space = convertToDTO(entity);
      if(space != null) {
        dtos.add(space);
      }
    }
    return dtos;
  }

  public List<SpaceDTO> getDeactvatedSpaces() {
    List<SpaceEntity> entities = spaceDAO.getDeactvatedSpaces(0,0);
    List<SpaceDTO> dtos = new ArrayList<SpaceDTO>();
    for (SpaceEntity entity : entities) {
      SpaceDTO space = convertToDTO(entity);
      if(space != null) {
        dtos.add(space);
      }
    }
    return dtos;
  }

  public List<SpaceDTO> getActiveByEndSubscriptionDate(Calendar fromDate, Calendar toDate) {
    List<SpaceEntity> entities = spaceDAO.getActiveByEndSubscriptionDate(fromDate,toDate);
    List<SpaceDTO> dtos = new ArrayList<SpaceDTO>();
    for (SpaceEntity entity : entities) {
      SpaceDTO space = convertToDTO(entity);
      if(space != null) {
        dtos.add(space);
      }
    }
    return dtos;
  }


  public Map<String,String> getAllSpaceNames() {
    List<SpaceEntity> entities = spaceDAO.findAll();
    Map<String,String> spaces = new HashMap<>();
    for(SpaceEntity entity : entities){
      String spaceName = spaceService.getSpaceByGroupId(entity.getGroupId()).getDisplayName();
      spaces.put(spaceName,entity.getGroupId());
    }
    return spaces;
  }

  public List<SpaceDTO> getSpaces(int offset, int limit) {
    if (offset < 0) {
      throw new IllegalArgumentException("Method getSpaces - Parameter 'offset' must be positive");
    }
    List<SpaceEntity> entities = spaceDAO.getSpaces(offset, limit);
    List<SpaceDTO> dtos = new ArrayList<SpaceDTO>();
    for (SpaceEntity entity : entities) {
      dtos.add(convertToDTO(entity));
    }
    return dtos;
  }

  public long getSpacesCount() {
    return spaceDAO.getSpacesCount();
  }

  public SpaceDTO updateSpace(SpaceDTO spaceDTO) throws Exception {
    Space space = spaceService.getSpaceByGroupId(spaceDTO.getGroupId());
    space.setType(CSSpaceApplicationHandler.NAME);
    spaceService.updateSpace(space);
    return save(spaceDTO);
  }

  public SpaceDTO saveSpace(SpaceDTO spaceDTO) throws Exception {
    if (spaceDTO == null) {
      throw new IllegalArgumentException("Method save - Parameter 'spaceDTO' is null");
    }
    if (!spaceDTO.verifySaveConditions()) {
      throw new IllegalStateException("spaceDTO is not valid: " + spaceDTO);
    }
    Group group = organizationService.getGroupHandler().findGroupById(spaceDTO.getName());
    if (group != null) {
      throw new EntityAlreadyExistsException("Space already exists with name: " + spaceDTO.getName());
    }

    Space space = new Space();
    space.setDisplayName(spaceDTO.getName());
    space.setDescription(spaceDTO.getDescription());
    space.setPrettyName(spaceDTO.getName());
    // Specific Space Type
    space.setType(CSSpaceApplicationHandler.NAME);
    space.setVisibility(Space.HIDDEN);
    space.setRegistration(Space.VALIDATION);
    space.setEditor(spaceDTO.getOwner());

    if(spaceDTO.getManagersGroupId() != null) {
      space = setManagers(spaceDTO, space);
    }
    if (spaceDTO.getMembers() != null) {
      space = setMembers(spaceDTO, space);
    }
    if(spaceDTO.getInvitedMembersGroupId() != null) {
      space = addInvitedMembers(spaceDTO, space);
    }

    // Verify that CS Space Type Handler is set in SpaceService
    // This will allow to create Space pages using specific layout templates
    csHandlerStarter.addHandlerToSpaceService();
    space = spaceService.createSpace(space, spaceDTO.getOwner());
    spaceDTO.setUrl(space.getUrl());
    spaceDTO.setGroupId(space.getGroupId());

    // Add customer members to the customers group
    addMembersToGlobalCustomerGroup(spaceDTO);
    // Add space members to space group
    for (String member : space.getMembers()) {
      try {
        SpaceUtils.addUserToGroupWithMemberMembership(member, space.getGroupId());
      } catch (Exception e) {
        LOG.warn("  Cannot add member '" + member + "' to space: " + space.getPrettyName(), e);
      }
    }

    // Add space managers to space group
    for (String manager : space.getManagers()) {
      try {
        SpaceUtils.addUserToGroupWithManagerMembership(manager, space.getGroupId());
      } catch (Exception e) {
        LOG.warn("  Cannot add manager '" + manager + "' to space: " + space.getPrettyName(), e);
      }
    }
    return save(spaceDTO);
  }

  private void addMembersToGlobalCustomerGroup(SpaceDTO spaceDTO) throws Exception {
    String customerGroupName = System.getProperty(CUSTOMERS_GROUP);
    if(StringUtils.isBlank(customerGroupName)){
      customerGroupName = "customers";
    }
    if (spaceDTO.getMembers() != null && !spaceDTO.getMembers().isEmpty()) {
      Set<String> members = new HashSet<String>();
      for (BasicEntityDTO member : spaceDTO.getMembers()) {
        if (member == null || member.computeId() == null) {
          continue;
        }
        if (organizationService.getUserHandler().findUserByName(member.computeId()) == null) {
          throw new IllegalStateException("User with id '" + member + "' not found");
        }
        SpaceUtils.addUserToGroupWithMemberMembership(member.computeId(), customerGroupName);
      }
    }
  }

  private SpaceDTO save(SpaceDTO spaceDTO) {
    SpaceEntity spaceEntity = spaceDAO.find(spaceDTO.getGroupId());
    if (spaceEntity == null) {
      spaceEntity = convertToEntity(spaceDTO, new SpaceEntity());
      spaceDAO.create(spaceEntity);
    } else {
      spaceEntity = convertToEntity(spaceDTO, spaceEntity);
      spaceDAO.update(spaceEntity);
    }
    return spaceDTO;
  }

  private Space setMembers(SpaceDTO spaceDTO, Space space) throws Exception {
    if (spaceDTO.getMembers() != null && !spaceDTO.getMembers().isEmpty()) {
      Set<String> members = new HashSet<String>();
      for (BasicEntityDTO member : spaceDTO.getMembers()) {
        if (member == null || member.computeId() == null) {
          continue;
        }
        if (organizationService.getUserHandler().findUserByName(member.computeId()) == null) {
          throw new IllegalStateException("User with id '" + member + "' not found");
        }
        // To improve the performance of the operation : add user to a space
        //SpaceUtils.addUserToGroupWithMemberMembership(member.computeId(), space.getGroupId());
        members.add(member.computeId());
      }
      members.addAll(Arrays.asList(space.getManagers()));

      members.add(spaceDTO.getTaskDefaultAssignee().computeId());

      space.setMembers(members.toArray(new String[0]));
    }
    return space;
  }

  private Space setManagers(SpaceDTO spaceDTO, Space space) throws Exception {
    if (spaceDTO.getManagersGroupId() != null) {
      Group group = organizationService.getGroupHandler().findGroupById(spaceDTO.getManagersGroupId().computeId());
      if (group == null) {
        throw new IllegalStateException("Managers group with id '" + spaceDTO.getManagersGroupId() + "' doesn't exist");
      }
      ListAccess<Membership> members = organizationService.getMembershipHandler().findAllMembershipsByGroup(group);
      Membership[] memberships = members.load(0, members.getSize());
      Set<String> managers = new HashSet<String>();
      for (Membership membership : memberships) {
        managers.add(membership.getUserName().trim());
      }

      space.setManagers(managers.toArray(new String[0]));
    }
    return space;
  }

  private Space addInvitedMembers(SpaceDTO spaceDTO, Space space) throws Exception {
    if (spaceDTO.getInvitedMembersGroupId() != null) {
      Group group = organizationService.getGroupHandler().findGroupById(spaceDTO.getInvitedMembersGroupId().computeId());
      if (group == null) {
        throw new IllegalStateException("Members group with id '" + spaceDTO.getInvitedMembersGroupId() + "' doesn't exist");
      }
      ListAccess<Membership> members = organizationService.getMembershipHandler().findAllMembershipsByGroup(group);
      Membership[] memberships = members.load(0, members.getSize());
      Set<String> invitedMembers = new HashSet<String>();
      for (Membership membership : memberships) {
        invitedMembers.add(membership.getUserName().trim());
      }
      List<String> memberList = new ArrayList<>();
      if(space.getMembers() != null && space.getMembers().length > 0){
        memberList = new ArrayList<>(Arrays.asList(space.getMembers()));
      }
      memberList.addAll(invitedMembers);
      space.setMembers(memberList.toArray(new String[0]));
    }
    return space;
  }

  public SpaceEntity convertToEntity(SpaceDTO spaceDTO, SpaceEntity spaceEntity) {
    spaceEntity.setGroupId(spaceDTO.getGroupId());
    spaceEntity.setTagIndex(spaceDTO.getTagIndex());
    spaceEntity.setManagersGroupId(spaceDTO.getManagersGroupId() == null ? null : spaceDTO.getManagersGroupId().computeId());
    spaceEntity.setInvitedMembersGroupId(spaceDTO.getInvitedMembersGroupId() == null ? null : spaceDTO.getInvitedMembersGroupId().computeId());
    spaceEntity.setTagPrefix(spaceDTO.getTagPrefix());
    spaceEntity.setTaskDefaultAssignee(spaceDTO.getTaskDefaultAssignee() == null ? null : spaceDTO.getTaskDefaultAssignee()
                                                                                                  .computeId());
    spaceEntity.setIncidentFlow(spaceDTO.getIncidentFlow());
    spaceEntity.setInformationFlow(spaceDTO.getInformationFlow());
    spaceEntity.setTaskPriority(spaceDTO.getTaskPriority());
    spaceEntity.setLimitTicketsNumber(spaceDTO.getLimitTicketsNumber());
    spaceEntity.setHosted(spaceDTO.isHosted());
    spaceEntity.setLanguage(spaceDTO.getLanguage());
    spaceEntity.setSla(spaceDTO.getSla());
    spaceEntity.setSubscription(spaceDTO.getSubscription());
    spaceEntity.setSubscriptionStartDate(convertToCalendar(spaceDTO.getSubscriptionStartDate()));
    spaceEntity.setSubscriptionEndDate(convertToCalendar(spaceDTO.getSubscriptionEndDate()));
    spaceEntity.setSubscriptionActive(spaceDTO.isSubscriptionActive());
    spaceEntity.setInternal(spaceDTO.getInternal());
    return spaceEntity;
  }

  public SpaceDTO convertToDTO(SpaceEntity spaceEntity) {
    Space space = spaceService.getSpaceByGroupId(spaceEntity.getGroupId());
    if (space != null) {
      SpaceDTO spaceDTO = new SpaceDTO();
      spaceDTO.setGroupId(spaceEntity.getGroupId());
      spaceDTO.setTagIndex(spaceEntity.getTagIndex());
      spaceDTO.setManagersGroupId(new BasicEntityDTO("@" + spaceEntity.getManagersGroupId(), spaceEntity.getManagersGroupId()));
      spaceDTO.setInvitedMembersGroupId(new BasicEntityDTO("@" + spaceEntity.getInvitedMembersGroupId(), spaceEntity.getInvitedMembersGroupId()));
      spaceDTO.setTagPrefix(spaceEntity.getTagPrefix());
      spaceDTO.setTaskDefaultAssignee(new BasicEntityDTO("@" + spaceEntity.getTaskDefaultAssignee(),
                                                         spaceEntity.getTaskDefaultAssignee()));
      spaceDTO.setTaskPriority(spaceDTO.getTaskPriority());
      spaceDTO.setLimitTicketsNumber(spaceEntity.getLimitTicketsNumber());

      Long supportTickets = ticketService.getTicketsCount(spaceEntity,IssueType.INCIDENT) + ticketService.getTicketsCount(spaceEntity,IssueType.INFORMATION);
      Long productTickets = ticketService.getTicketsCount(spaceEntity,IssueType.PRODUCT);
      spaceDTO.setSupportTickets(supportTickets);
      spaceDTO.setProductTickets(productTickets);
      spaceDTO.setTotalTickets(supportTickets + productTickets);
      spaceDTO.setInformationFlow(spaceEntity.getInformationFlow());
      spaceDTO.setIncidentFlow(spaceEntity.getIncidentFlow());
      spaceDTO.setLanguage(spaceEntity.getLanguage());

      spaceDTO.setName(space.getDisplayName());
      spaceDTO.setDescription(space.getDescription());
      spaceDTO.setHosted(spaceEntity.isHosted());
      spaceDTO.setSla(spaceEntity.getSla());
      spaceDTO.setSubscription(spaceEntity.getSubscription());
      if(spaceEntity.getSubscriptionStartDate()!=null){
        spaceDTO.setSubscriptionStartDate(convertDate(DEFAULT_DATE_FORMAT,spaceEntity.getSubscriptionStartDate().getTime()));

      }else{
        spaceDTO.setSubscriptionStartDate("");
      }
      if(spaceEntity.getSubscriptionEndDate()!=null){
        spaceDTO.setSubscriptionEndDate(convertDate(DEFAULT_DATE_FORMAT,spaceEntity.getSubscriptionEndDate().getTime()));

      }else{
        spaceDTO.setSubscriptionEndDate("");
      }
      spaceDTO.setSubscriptionActive(spaceEntity.isSubscriptionActive());
      spaceDTO.setInternal(spaceEntity.getInternal());
      return spaceDTO;
    } else {
      return null;
    }
  }

  private int getSupportTicketsNumber(SpaceEntity spaceEntity) {
    List<TicketDTO> tickets = ticketService.getTicketsOfSpace(spaceEntity.getGroupId());
    List<TicketDTO> SupportTickets = new ArrayList<TicketDTO>();
    for (TicketDTO ticket : tickets){
      if(IssueType.INFORMATION.equals(ticket.getType()) || IssueType.INCIDENT.equals(ticket.getType())){
        SupportTickets.add(ticket);
      }
    }
    return SupportTickets.size();
  }

  private int getProductTicketsNumber(SpaceEntity spaceEntity){
    List<TicketDTO> tickets = ticketService.getTicketsOfSpace(spaceEntity.getGroupId());
    List<TicketDTO> ProductTickets = new ArrayList<TicketDTO>();
    for (TicketDTO ticket : tickets){
      if(IssueType.PRODUCT.equals(ticket.getType())){
        ProductTickets.add(ticket);
      }
    }
    return ProductTickets.size();
  }

  private int getTotalTicketsNumber(SpaceEntity spaceEntity){
    List<TicketDTO> tickets = ticketService.getTicketsOfSpace(spaceEntity.getGroupId());
    return tickets.size();
  }

  public Calendar convertToCalendar(String dateToFormat){
    Calendar cal = Calendar.getInstance();
    try {
      Date date = DEFAULT_DATE_FORMAT.parse(dateToFormat);
      cal.setTime(date);
    } catch (Exception e) {
      LOG.debug("Error when converting {} to a date using formatter {} ", dateToFormat, DEFAULT_DATE_FORMAT);
    }
    return cal;
  }


  public String convertDate(DateFormat dateFormat, Date date) {

    if (date != null) {
      return dateFormat.format(date);
    } else {
      return null;
    }
  }


}
