/*
 *
 *  * 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.*;
import org.exoplatform.cs.entity.SpaceEntity;
import org.exoplatform.cs.exception.EntityAlreadyExistsException;
import org.exoplatform.services.listener.ListenerService;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;
import org.exoplatform.services.organization.*;
import org.exoplatform.social.core.manager.IdentityManager;
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.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 IdentityManager identityManager;

  private SpaceService        spaceService;

  private OrganizationService organizationService;

  private TicketService       ticketService;

  private ListenerService listenerService;

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



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

  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());
    SpaceEntity entity = getSpaceEntityByGroupId(spaceDTO.getGroupId());

    if(spaceDTO.getName() != null && !space.getDisplayName().equals(spaceDTO.getName()))
    {
      spaceService.renameSpace(space,spaceDTO.getName());
    }
    ///

    Map<String,String> changes = new HashMap<>();

    //Subscription Type Update
    if(spaceDTO.getSubscription() != null && !entity.getSubscription().equals(spaceDTO.getSubscription()))
    {
      changes.put(CSConstants.OLD_SUBSCRIPTION,entity.getSubscription());
      changes.put(CSConstants.NEW_SUBSCRIPTION,spaceDTO.getSubscription());
      changes.put(CSConstants.ACTION_NAME,"SUBSCRIPTION_TYPE_CHANGED");
      listenerService.broadcast(CSConstants.SPACE_UPDATED , spaceDTO, changes);
    }

    //Start Date Update
    if(spaceDTO.getSubscriptionStartDate() != null && !convertDate(DEFAULT_DATE_FORMAT,entity.getSubscriptionStartDate().getTime()).equals(spaceDTO.getSubscriptionStartDate()))
    {
      changes.put(CSConstants.OLD_START_DATE,convertDate(DEFAULT_DATE_FORMAT,entity.getSubscriptionStartDate().getTime()));
      changes.put(CSConstants.NEW_START_DATE,spaceDTO.getSubscriptionStartDate());
      changes.put(CSConstants.ACTION_NAME,"SUBSCRIPTION_START_DATE_CHANGED");
      listenerService.broadcast(CSConstants.SPACE_UPDATED , spaceDTO, changes);
    }

    //EnDate Update
    if(spaceDTO.getSubscriptionEndDate() != null && !convertDate(DEFAULT_DATE_FORMAT,entity.getSubscriptionEndDate().getTime()).equals(spaceDTO.getSubscriptionEndDate()))
    {
      changes.put(CSConstants.OLD_END_DATE,convertDate(DEFAULT_DATE_FORMAT,entity.getSubscriptionEndDate().getTime()));
      changes.put(CSConstants.NEW_END_DATE,spaceDTO.getSubscriptionEndDate());
      changes.put(CSConstants.ACTION_NAME,"SUBSCRIPTION_END_DATE_CHANGED");
      listenerService.broadcast(CSConstants.SPACE_UPDATED, spaceDTO, changes);
    }

    //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);
    String shortName = SpaceUtils.cleanString(spaceDTO.getName());
    space.setGroupId("/spaces/" + shortName);
    space.setUrl(shortName);
    space.setTemplate(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);
    }


    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(), UserStatus.ENABLED) != null ) {
           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) {
        if ( organizationService.getUserHandler().findUserByName(membership.getUserName().trim(), UserStatus.ENABLED) != null ) {
          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());
    spaceEntity.setCustomerExtensionVersion(spaceDTO.getCustomerExtensionVersion());
    spaceEntity.setInstalledPatchVersion(spaceDTO.getInstalledPatchVersion());
    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.setActifTickets(getActifTicketsNumber(spaceEntity));
      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());
      spaceDTO.setCustomerExtensionVersion(spaceEntity.getCustomerExtensionVersion());
      spaceDTO.setInstalledPatchVersion(spaceEntity.getInstalledPatchVersion());
      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 getActifTicketsNumber(SpaceEntity spaceEntity) {
    List<TicketDTO> tickets = ticketService.getTicketsOfSpace(spaceEntity.getGroupId());
    List<TicketDTO> ActifTickets = new ArrayList<TicketDTO>();
    for (TicketDTO ticket : tickets) {
      if (!CSConstants.STATUS_CLOSED.equals(ticket.getStatus())) {
        if ((!CSConstants.STATUS_INPROGRESS.equals(ticket.getStatus())) || (CSConstants.STATUS_INPROGRESS.equals(ticket.getStatus()) && !Owner.PM.equals(ticket.getOwner()))) {
          ActifTickets.add(ticket);
        }
      }
    }
    return ActifTickets.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;
    }
  }


}
