package org.nuxeo.ecm.restapi.server.jaxrs.firstvoices;

import ca.firstvoices.rest.data.ContactUsRequest;
import ca.firstvoices.rest.services.SimpleMailService;
import java.util.Map;
import java.util.WeakHashMap;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.POST;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.nuxeo.ecm.core.api.CoreSession;
import org.nuxeo.ecm.core.api.DocumentModel;
import org.nuxeo.ecm.core.api.IdRef;
import org.nuxeo.ecm.core.api.UnrestrictedSessionRunner;
import org.nuxeo.ecm.webengine.model.WebObject;
import org.nuxeo.ecm.webengine.model.impl.DefaultObject;
import org.nuxeo.runtime.api.Framework;

@WebObject(type = "contact_us")
@Produces(MediaType.APPLICATION_JSON)
public class ContactUsObject extends DefaultObject {

  private static final Log log = LogFactory.getLog(ContactUsObject.class);
  private static final SpamGate spamGate;

  static {
    spamGate = new TimeBasedSpamGate();
  }


  @POST
  public Response contactUs(ContactUsRequest requestBody) {
    if (!spamGate.requestCanProceed(getContext().getRequest(), requestBody)) {
      // 429, too many requests
      return Response.status(429).build();
    }

    CoreSession session = getContext().getCoreSession();

    DialectEmailResolver dialectResolver = new DialectEmailResolver(session,
        requestBody.getIdOfDialect());

    dialectResolver.runUnrestricted();

    if (!dialectResolver.isValid()) {
      return Response.status(400).entity("Contact Us not valid for this dialect").build();
    }

    final String sourceEmail = requestBody.getEmailAddress();
    if (sourceEmail == null || sourceEmail.length() == 0 || !validateEmail(sourceEmail)) {
      return Response.status(400).entity("Specified email address not valid").build();
    }

    final String messageBody = requestBody.getMessageBody();
    if (messageBody == null || messageBody.length() == 0 || messageBody.length() > 2000) {
      return Response.status(400).entity("Message body empty or exceeds 2000 characters").build();
    }

    final String name = requestBody.getName();
    if (name == null || name.length() == 0 || name.length() > 255) {
      return Response.status(400).entity("Name empty or exceeds 255 characters").build();
    }

    String dialectEmail = dialectResolver.getResolvedEmail();

    log.info(String.format("Contact us message valid for dialect %s, will send message to %s from "
            + "%s, with body: %s",
        dialectResolver.getDialectTitle(),
        dialectEmail,
        sourceEmail,
        messageBody));

    final StringBuffer formattedMessageBody = new StringBuffer();

    formattedMessageBody.append("You have received a new contact us message in FirstVoices\r\n");
    formattedMessageBody.append("From: ");
    formattedMessageBody.append(requestBody.getName());
    formattedMessageBody.append("\r\n");
    formattedMessageBody.append("Email Address (unverified): ");
    formattedMessageBody.append(requestBody.getEmailAddress());
    formattedMessageBody.append("\r\n");
    formattedMessageBody.append("Message text: \r\n");
    formattedMessageBody.append(requestBody.getMessageBody());

    SimpleMailService mailService = Framework.getService(SimpleMailService.class);

    mailService.sendMessage(dialectEmail,
        formattedMessageBody.toString(),
        "FirstVoices: New Contact Us Message");

    return Response.status(202).build();
  }

  private static boolean validateEmail(String email) {
    boolean valid = true;
    try {
      InternetAddress addr = new InternetAddress(email);
      addr.validate();
    } catch (AddressException ex) {
      valid = false;
    }
    return valid;
  }

  private interface SpamGate {

    boolean requestCanProceed(HttpServletRequest request, ContactUsRequest contactUsRequest);
  }

  private static class TimeBasedSpamGate implements SpamGate {

    static final long GLOBAL_COOLDOWN = 5L * 1000L;
    static final long PER_DIALECT_COOLDOWN = 30L * 1000L;
    static final long PER_REMOTE_ADDR_COOLDOWN = 300L * 1000L;

    long lastMailSentTime = 0;
    final Map<String, Long> dialectLastEmailTimes = new WeakHashMap<>();
    final Map<String, Long> remoteAddressLastEmailTimes = new WeakHashMap<>();

    @Override
    public boolean requestCanProceed(
        final HttpServletRequest request, final ContactUsRequest contactUsRequest) {

      final long now = System.currentTimeMillis();
      final String remoteIp = request.getRemoteAddr();

      if (now - lastMailSentTime < GLOBAL_COOLDOWN) {
        return false;
      }

      final Long dialectLastEmailTime =
          dialectLastEmailTimes.get(contactUsRequest.getIdOfDialect());
      if (dialectLastEmailTime != null && (now - dialectLastEmailTime < PER_DIALECT_COOLDOWN)) {
        return false;
      }

      final Long remoteAddressLastEmailTime = remoteAddressLastEmailTimes.get(remoteIp);
      if (remoteAddressLastEmailTime != null && (now - remoteAddressLastEmailTime
          < PER_REMOTE_ADDR_COOLDOWN)) {
        return false;
      }

      this.lastMailSentTime = now;
      dialectLastEmailTimes.put(contactUsRequest.getIdOfDialect(), now);
      remoteAddressLastEmailTimes.put(remoteIp, now);

      return true;
    }
  }


  private static class DialectEmailResolver extends UnrestrictedSessionRunner {

    private String dialectId;
    private boolean valid = false;
    private String resolvedEmail = null;
    private String dialectTitle = null;

    public DialectEmailResolver(CoreSession session, String dialectId) {
      super(session);
      this.dialectId = dialectId;
    }

    @Override
    public void run() {

      DocumentModel dialectDocument = this.session.getDocument(new IdRef(dialectId));
      if (!"FVDialect".equals(dialectDocument.getType())) {
        throw new IllegalArgumentException("Not a dialect");
      }
      this.dialectTitle = dialectDocument.getTitle();

      final String dialectEmail = (String) dialectDocument.getPropertyValue(
          "fvdialect:contact_email");

      if (dialectEmail == null || dialectEmail.length() == 0 || !validateEmail(dialectEmail)) {
        this.valid = false;
      } else {
        this.valid = true;
        this.resolvedEmail = dialectEmail;
      }
    }

    public String getResolvedEmail() {
      return resolvedEmail;
    }

    public String getDialectTitle() {
      return dialectTitle;
    }

    public boolean isValid() {
      return valid;
    }
  }

}

@@ -3507,49 +3507,8 @@
 il,%0A
-        formattedMessageBody.toString(),%0A
     
@@ -3548,16 +3548,49 @@
 Message%22
+, formattedMessageBody.toString()
 );%0A%0A    

Fix body/subject order