package org.exoplatform.commons.notification.impl.jpa.email;

import static org.exoplatform.commons.notification.impl.jpa.EntityConverter.convertParamsEntityToParams;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.exoplatform.commons.api.notification.NotificationContext;
import org.exoplatform.commons.api.notification.model.ArgumentLiteral;
import org.exoplatform.commons.api.notification.model.NotificationInfo;
import org.exoplatform.commons.api.notification.model.PluginKey;
import org.exoplatform.commons.api.notification.model.UserSetting;
import org.exoplatform.commons.api.notification.service.storage.MailNotificationStorage;
import org.exoplatform.commons.api.persistence.DataInitializer;
import org.exoplatform.commons.api.persistence.ExoTransactional;
import org.exoplatform.commons.notification.impl.jpa.email.dao.MailDigestDAO;
import org.exoplatform.commons.notification.impl.jpa.email.dao.MailNotifDAO;
import org.exoplatform.commons.notification.impl.jpa.email.dao.MailParamDAO;
import org.exoplatform.commons.notification.impl.jpa.email.entity.MailDigestEntity;
import org.exoplatform.commons.notification.impl.jpa.email.entity.MailNotifEntity;
import org.exoplatform.commons.notification.impl.jpa.email.entity.MailParamEntity;
import org.exoplatform.commons.notification.job.NotificationJob;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;

public class JPAMailNotificationStorage implements MailNotificationStorage {

  private static final int                 QUERY_LIMIT   = 100;

  private static final Log                 LOG           = ExoLogger.getLogger(JPAMailNotificationStorage.class);

  public static final String               DIGEST_DAILY  = "daily";

  public static final String               DIGEST_WEEKLY = "weekly";

  @SuppressWarnings("rawtypes")
  public static final ArgumentLiteral<Map> WEEKLY_NOTIFS = new ArgumentLiteral<Map>(Map.class, "weekly_notifications");

  @SuppressWarnings("rawtypes")
  public static final ArgumentLiteral<Map> DAILY_NOTIFS  = new ArgumentLiteral<Map>(Map.class, "daily_notifications");

  private MailNotifDAO                     mailNotifDAO;

  private MailDigestDAO                    mailDigestDAO;

  private MailParamDAO                     mailParamDAO;

  public JPAMailNotificationStorage(MailNotifDAO mailNotifDAO,
                                    MailDigestDAO mailDigestDAO,
                                    MailParamDAO mailParamDAO,
                                    DataInitializer dataInitializer) {
    this.mailNotifDAO = mailNotifDAO;
    this.mailDigestDAO = mailDigestDAO;
    this.mailParamDAO = mailParamDAO;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  @ExoTransactional
  public void save(NotificationInfo message) throws Exception {
    MailNotifEntity notifEntity = new MailNotifEntity();

    notifEntity.setSender(message.getFrom());
    notifEntity.setOrder(message.getOrder());
    notifEntity.setType(message.getKey().getId());
    notifEntity.setCreationDate(message.getDateCreated());
    notifEntity = mailNotifDAO.create(notifEntity);
    message.setId(String.valueOf(notifEntity.getId()));

    MailDigestEntity digestEntityDaily = new MailDigestEntity();
    digestEntityDaily.setNotification(notifEntity).setType(DIGEST_DAILY);
    mailDigestDAO.create(digestEntityDaily);

    MailDigestEntity digestEntityWeekly = new MailDigestEntity();
    digestEntityWeekly.setNotification(notifEntity).setType(DIGEST_WEEKLY);
    mailDigestDAO.create(digestEntityWeekly);

    Map<String, String> parameters = message.getOwnerParameter();
    if (parameters != null && !parameters.isEmpty()) {
      for (String key : parameters.keySet()) {
        MailParamEntity paramEntity = new MailParamEntity();
        paramEntity.setName(key);
        paramEntity.setValue(parameters.get(key));
        paramEntity.setNotification(notifEntity);
        mailParamDAO.create(paramEntity);
      }
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  @ExoTransactional
  public Map<PluginKey, List<NotificationInfo>> getByUser(NotificationContext context, UserSetting setting) {
    Map<PluginKey, List<NotificationInfo>> notificationData = new LinkedHashMap<PluginKey, List<NotificationInfo>>();
    boolean isWeekly = context.value(NotificationJob.JOB_WEEKLY);
    if (isWeekly) {
      for (String pluginId : setting.getWeeklyPlugins()) {
        putMap(notificationData, PluginKey.key(pluginId), getWeeklyNotifs(context, pluginId, setting.getUserId()));
      }
    }
    //
    boolean isDaily = context.value(NotificationJob.JOB_DAILY);
    if (isDaily) {
      for (String pluginId : setting.getDailyPlugins()) {
        putMap(notificationData, PluginKey.key(pluginId), getDailyNotifs(context, pluginId, setting.getUserId()));
      }
    }
    return notificationData;
  }

  /**
   * {@inheritDoc}
   */
  @SuppressWarnings("unchecked")
  @Override
  @ExoTransactional
  public void removeMessageAfterSent(NotificationContext context) throws Exception {
    boolean isWeekly = context.value(NotificationJob.JOB_WEEKLY);

    // Notifications that was sent
    Map<String, List<NotificationInfo>> generalWeeklyNotificationData = null;
    if (isWeekly) {
      generalWeeklyNotificationData = context.value(WEEKLY_NOTIFS);
    } else {
      generalWeeklyNotificationData = context.value(DAILY_NOTIFS);
    }
    if (generalWeeklyNotificationData == null) {
      mailDigestDAO.deleteAllDigestsOfType(isWeekly ? DIGEST_WEEKLY : DIGEST_DAILY);
    } else {
      Set<Long> mailNotifsIds = new HashSet<>();
      Collection<List<NotificationInfo>> notificationsLists = generalWeeklyNotificationData.values();
      for (List<NotificationInfo> notificationsList : notificationsLists) {
        if (notificationsList == null) {
          continue;
        }
        for (NotificationInfo notificationInfo : notificationsList) {
          try {
            mailNotifsIds.add(Long.parseLong(notificationInfo.getId()));
          } catch (NumberFormatException e) {
            LOG.warn("can't parse mail notification with id: " + notificationInfo.getId());
          }
        }
      }
      if (mailNotifsIds != null && !mailNotifsIds.isEmpty()) {
        mailDigestDAO.deleteDigestsOfTypeByNotificationsIds(mailNotifsIds, isWeekly ? DIGEST_WEEKLY : DIGEST_DAILY);
      }
    }
    List<MailNotifEntity> allNotificationsWithoutDigests;
    do {
      allNotificationsWithoutDigests = mailNotifDAO.getAllNotificationsWithoutDigests(0, QUERY_LIMIT);
      if (allNotificationsWithoutDigests != null && !allNotificationsWithoutDigests.isEmpty()) {
        mailParamDAO.deleteParamsOfNotifications(allNotificationsWithoutDigests);
        mailNotifDAO.deleteAll(allNotificationsWithoutDigests);
      }
    } while (allNotificationsWithoutDigests.size() == QUERY_LIMIT);
  }

  private List<NotificationInfo> getWeeklyNotifs(NotificationContext context, String pluginId, String userId) {
    return getNotificationsByDigestAndPluginId(context, WEEKLY_NOTIFS, pluginId, userId);
  }

  private List<NotificationInfo> getDailyNotifs(NotificationContext context, String pluginId, String userId) {
    return getNotificationsByDigestAndPluginId(context, DAILY_NOTIFS, pluginId, userId);
  }

  private List<NotificationInfo> getNotificationsByDigestAndPluginId(NotificationContext context,
                                                                     @SuppressWarnings("rawtypes") ArgumentLiteral<Map> notifsDigestArgument,
                                                                     String pluginId,
                                                                     String userId) {
    // Get notifications by plugin id from context
    @SuppressWarnings("unchecked")
    Map<String, List<NotificationInfo>> notificationsByPluginId = context.value(notifsDigestArgument);
    if (notificationsByPluginId == null) {
      notificationsByPluginId = new HashMap<>();
      context.append(notifsDigestArgument, notificationsByPluginId);
    }

    List<NotificationInfo> notificationInfos = null;
    if (notificationsByPluginId.containsKey(pluginId)) {
      // Get notifications by plugin id from context
      notificationInfos = notificationsByPluginId.get(pluginId);
    } else {
      // Get notifications by plugin id from Storage
      notificationInfos = new ArrayList<NotificationInfo>();
      List<MailNotifEntity> notifEntities = notifsDigestArgument == WEEKLY_NOTIFS ? getNotifsByWeek(pluginId)
                                                                                  : getNotifsByDate(context, pluginId);
      for (MailNotifEntity mailNotifEntity : notifEntities) {
        NotificationInfo model = fillModel(mailNotifEntity, null);
        notificationInfos.add(model);
      }
      // Set notifications to process by plugin id in context
      notificationsByPluginId.put(pluginId, notificationInfos);
    }

    List<NotificationInfo> resultNotificationInfos = new ArrayList<>();
    for (NotificationInfo notificationInfo : notificationInfos) {
      NotificationInfo resultNotificationInfo = notificationInfo.clone(false);
      resultNotificationInfo.setTo(userId);
      resultNotificationInfos.add(resultNotificationInfo);
    }
    return resultNotificationInfos;
  }

  private void putMap(Map<PluginKey, List<NotificationInfo>> notificationData, PluginKey key, List<NotificationInfo> values) {
    if (notificationData.containsKey(key)) {
      List<NotificationInfo> messages = notificationData.get(key);
      for (NotificationInfo notificationMessage : values) {
        if (messages.size() == 0 || messages.contains(notificationMessage) == false) {
          messages.add(notificationMessage);
        }
      }
      //
      if (messages.size() > 0) {
        notificationData.put(key, messages);
      }
    } else if (values.size() > 0) {
      notificationData.put(key, values);
    }
  }

  private NotificationInfo fillModel(MailNotifEntity notifEntity, String userId) {
    if (notifEntity == null)
      return null;
    NotificationInfo message = NotificationInfo.instance()
                                               .setFrom(notifEntity.getSender())
                                               .setTo(userId)
                                               .setOrder(notifEntity.getOrder())
                                               .key(notifEntity.getType())
                                               .setOwnerParameter(convertParamsEntityToParams(notifEntity.getParameters()))
                                               .setLastModifiedDate(notifEntity.getCreationDate())
                                               .setId(String.valueOf(notifEntity.getId()));

    return message;
  }

  private List<MailNotifEntity> getNotifsByDate(NotificationContext context, String pluginId) {
    String dayName = context.value(NotificationJob.DAY_OF_JOB);
    return mailNotifDAO.getNotifsByPluginAndDay(pluginId, dayName);
  }

  private List<MailNotifEntity> getNotifsByWeek(String pluginId) {
    Calendar calendar = Calendar.getInstance();
    calendar.add(Calendar.DATE, -7);
    Calendar oneWeekAgo = calendar;
    return mailNotifDAO.getNotifsByPluginAndWeek(pluginId, oneWeekAgo);
  }

}
