package de.bornemisza.users.subscriber;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.util.UUID;
import java.util.logging.Logger;
import java.util.stream.Collectors;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.inject.Inject;
import javax.mail.internet.InternetAddress;

import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.core.IMap;
import com.hazelcast.core.ITopic;
import com.hazelcast.core.Message;
import com.hazelcast.core.MessageListener;

import de.bornemisza.couchdb.entity.User;
import de.bornemisza.users.MailSender;

public abstract class AbstractConfirmationMailListener implements MessageListener<User> {

    @Inject
    protected MailSender mailSender;

    @Inject
    protected HazelcastInstance hazelcast;

    abstract String getRequestTopicName();
    abstract String getRequestMapName();
    abstract String getMailSubject();
    abstract String getHtmlTemplateFileName();
    abstract String getTextTemplateFileName();
    abstract String getFallbackHtmlTemplate();
    abstract String getFallbackTextTemplate();

    private ITopic<User> requestTopic;
    private IMap<String, User> requestMap;
    private String registrationId;

    protected final String FQDN = System.getProperty("FQDN");

    public AbstractConfirmationMailListener() {
    }

    // Constructor for Unit Tests
    public AbstractConfirmationMailListener(ITopic<User> aTopic, IMap<String, User> aMap, MailSender mailSender) {
        this.requestTopic = aTopic;
        this.requestMap = aMap;
        this.mailSender = mailSender;
    }

    @PostConstruct
    public void init() {
        this.requestTopic = hazelcast.getTopic(getRequestTopicName());
        this.registrationId = requestTopic.addMessageListener(this);
        this.requestMap = hazelcast.getMap(getRequestMapName());
    }

    private InternetAddress createRecipient(User user) {
        try {
            return new InternetAddress(user.getEmail().getAddress(), user.getName());
        }
        catch (UnsupportedEncodingException ex) {
            return user.getEmail();
        }
    }

    @Override
    public void onMessage(Message<User> msg) {
        User user = msg.getMessageObject();
        Logger.getAnonymousLogger().info("Request detected on Topic " + getRequestTopicName() + " for: " + user.toString());
        String uuid = UUID.randomUUID().toString();
        User previousValue = this.requestMap.putIfAbsent(uuid, user);
        if (previousValue == null) {
            boolean mailSent = sendConfirmationMail(user, uuid);
            if (!mailSent) {
                this.requestMap.remove(uuid);
            }
        } 
        else {
            Logger.getAnonymousLogger().warning("UUID clash: " + uuid);
        }
        Logger.getAnonymousLogger().info("Unconfirmed Requests in " + getRequestMapName() + ": " + requestMap.size());
    }

    private boolean sendConfirmationMail(User user, String uuid) {
        InternetAddress recipient = createRecipient(user);
        String textContent = createContent(user, uuid, getTextTemplateFileName(), getFallbackTextTemplate());
        String htmlContent = createContent(user, uuid, getHtmlTemplateFileName(), getFallbackHtmlTemplate());
        boolean success = mailSender.sendMail(recipient, getMailSubject(), textContent, htmlContent);
        if (success) {
            Logger.getAnonymousLogger().info("Sent " + uuid + " to " + recipient.getAddress());
        }
        else {
            Logger.getAnonymousLogger().info("Sending " + uuid + " to " + recipient.getAddress() + " failed!");
        }
        return success;
    }

    @PreDestroy
    private void dispose() {
        requestTopic.removeMessageListener(registrationId);
        Logger.getAnonymousLogger().info("Message Listener " + registrationId + " removed.");
    }

    private String createContent(User user, String uuid, String template, String fallbackTemplate) {
        String content;
        try {
            content = getResourceFile(template);
        }
        catch (IOException ioe) {
            content = fallbackTemplate;
        }
        return content.replace("$FQDN$", FQDN).replace("$NAME$", user.getName()).replace("$UUID$", uuid);
    }

    private String getResourceFile(String fileName) throws IOException {
        FileInputStream inputStream = new FileInputStream(getClass().getClassLoader().getResource(fileName).getFile());
        try (BufferedReader buffer = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
            return buffer.lines().collect(Collectors.joining("\n"));
        }
    }

}

@@ -2392,24 +2392,86 @@
 geObject();%0A
+        this.requestMap.lock(user.getId());%0A        try %7B%0A    
         Logg
@@ -2587,24 +2587,28 @@
 ));%0A        
+ 
+   
 String uuid 
@@ -2643,24 +2643,28 @@
 ();%0A        
+   
+ 
 User previou
@@ -2717,24 +2717,28 @@
 r);%0A        
+    
 if (previous
@@ -2758,32 +2758,36 @@
 ) %7B%0A            
+ 
+   
 boolean mailSent
@@ -2831,24 +2831,28 @@
             
+   
+ 
 if (!mailSen
@@ -2852,24 +2852,28 @@
 mailSent) %7B%0A
+    
             
@@ -2918,26 +2918,33 @@
         
+ 
-%7D%0A
+   %7D%0A   
         
 %7D %0A     
@@ -2939,11 +2939,16 @@
     
+ 
 %7D %0A
+    
     
@@ -2962,32 +2962,36 @@
 e %7B%0A            
+ 
+   
 Logger.getAnonym
@@ -3034,34 +3034,42 @@
  uuid);%0A        
-%7D%0A
+    %7D%0A    
         Logger.g
@@ -3167,24 +3167,112 @@
 ap.size());%0A
+        %7D%0A        finally %7B%0A            this.requestMap.unlock(user.getId());%0A        %7D%0A
     %7D%0A%0A    p

implement locking to prevent multiple sends