package eu.dnetlib.data.emailSender;

import eu.dnetlib.data.claims.entity.Claim;
import eu.dnetlib.data.claims.entity.Context;
import eu.dnetlib.data.claims.entity.Notification;
import eu.dnetlib.data.claims.entity.Project;
import eu.dnetlib.data.claims.handler.*;
import eu.dnetlib.data.claims.sql.SQLStoreException;
import eu.dnetlib.data.claims.utils.ClaimUtils;
import eu.dnetlib.data.claims.utils.ManagerUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;

import javax.mail.*;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import java.text.SimpleDateFormat;
import java.util.*;

public class EmailSender implements Runnable {

    private static final Logger logger = LogManager.getLogger(EmailSender.class);

    @Autowired
    private FetchClaimHandler fetchClaimHandler = null;
    @Autowired
    private FetchProjectHandler fetchProjectHandler = null;
    @Autowired
    private FetchNotificationHandler fetchNotificationHandler = null;
    @Autowired
    private NotificationHandler notificationHandler = null;
    @Autowired
    private FetchContextHandler fetchContextHandler = null;

    @Autowired
    private String defaultFrequencyInHours;

//    @Autowired
//    private CommunityUtils communityUtils;

    @Autowired
    private ManagerUtils managerUtils;

    private static String manageCommunityUserNotificationsPage;
    private static String openaireProjectClaimsPage;
    private static String openaireCommunityClaimsPage;
    private static String username;
    private static String password;
    private static String host;
    private static String port;
    private static String from;
    private static String auth;
    private static String sslProtocols;
    private static String contactMail;
    private static String specialRecipients;
    private static String enabledCommunities;
    private static boolean notifyCommunityManagers;
    private static boolean notifyProjectManagers;

    @Override
    public void run() {
        logger.info("EmailSender thread is running. " + host);
        logger.info("Special Recipients  " + specialRecipients);
        logger.info("Enabled Communities " + enabledCommunities);

        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date date = new Date();
        String dateTo = (format.format(date));
        if(notifyCommunityManagers){
            defaultEmails_For_CommunityClaims(dateTo, format);  // daily for managers not in notification table
            notificationEmails_For_CommunityClaims(dateTo, format);
        }
        if(notifyProjectManagers) {
//        defaultEmails_For_ProjectClaims(dateTo, format);    // daily for managers not in notification table
//        notificationEmails_For_ProjectClaims(dateTo, format);
        }
    }

    public void defaultEmails_For_ProjectClaims(String dateTo, SimpleDateFormat format) {
        Map<String, List<String>> managersOfProject = new HashMap<String, List<String>>();
        Project project;
        List<Claim> claims = null;
        List<String> types = new ArrayList<String>();

        types.add(ClaimUtils.PROJECT);

        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.HOUR_OF_DAY, -(Integer.parseInt(defaultFrequencyInHours)-1));
        calendar.add(Calendar.MINUTE, -59);
        calendar.add(Calendar.SECOND, -59);
        Date date = calendar.getTime();
        String dateFrom=(format.format(date));

        logger.debug("Sending emails for project claims between " + dateFrom + " and "+dateTo);

        try {
            // Get all claims between dateFrom and dateTo which satisfy source_type == "project" or target_type == "project"
            claims = fetchClaimHandler.fetchClaimsByDate(dateFrom, dateTo, null, null, "", "source", true, types, false);
        } catch (SQLStoreException|Exception e) {
            logger.error("Could not fetch claims by date from "+dateFrom+" to "+dateTo, e);
        }

        for (Claim claim: claims) {
            if (claim.getSourceType().equals("project")) {
                project = (Project)claim.getSource();
            } else {
                project = (Project)claim.getTarget();
            }

            if (!managersOfProject.containsKey(project.getOpenaireId())) {

                // specialRecipients are used currently for testing purposes
                List<String> tmpManagers = null;
                if (specialRecipients != null && !specialRecipients.isEmpty()) {
                    tmpManagers = new ArrayList<>(Arrays.asList(specialRecipients.split("\\s*,\\s*")));
                    logger.debug("Special recipients: " + specialRecipients);

                    if(tmpManagers != null) {
                        Iterator itr = tmpManagers.iterator();
                        while (itr.hasNext()) {
                            String manager = (String) itr.next();
                            Notification notification = null;
                            try {
                                notification = fetchNotificationHandler.fetchNotification(project.getOpenaireId(), manager);
                            } catch (Exception e) {
                                e.printStackTrace();
                            } catch (SQLStoreException e) {
                                e.printStackTrace();
                            }
                            if (notification != null) {
                                itr.remove();
                            }
                        }
                    }

                    managersOfProject.put(project.getOpenaireId(), tmpManagers);
                }

                // Send emails to actual project managers instead of special recipients
                List<String> managers = null;
                try {
                    managers = fetchProjectHandler.fetchContactEmailsByProjectId(project.getOpenaireId());
                    logger.debug("All actual Managers of project " + project.getOpenaireId() + ": "+managers);

                    if(managers != null) {
                        Iterator itr = managers.iterator();
                        while (itr.hasNext()) {
                            String manager = (String) itr.next();
                            Notification notification = fetchNotificationHandler.fetchNotification(project.getOpenaireId(), manager);
                            //if (notification != null && (!notification.isNotify() || notification.getFrequency() != 24)) {
                            if (notification != null) {
                                itr.remove();
                            }
                        }
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                } catch (SQLStoreException e) {
                    e.printStackTrace();
                }

                logger.debug("Managers of project (not in notification table) " + project.getOpenaireId() + ": "+managers);
                /*
                managersOfProject.put(project.getOpenaireId(), managers);
                */

                if (managersOfProject.get(project.getOpenaireId()) != null &&
                        !managersOfProject.get(project.getOpenaireId()).isEmpty()) {
                    send(project.getOpenaireId(), project.getName(), "project", managersOfProject.get(project.getOpenaireId()));
                }
            }
        }
    }

    public void defaultEmails_For_CommunityClaims(String dateTo, SimpleDateFormat format) {
        Map<String, List<String>> managersOfCommunity = new HashMap<String, List<String>>();
        Context context;
        List<Claim> claims = null;
        List<String> types = new ArrayList<String>();

        types.add(ClaimUtils.CONTEXT);

        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.HOUR_OF_DAY, -(Integer.parseInt(defaultFrequencyInHours)-1));
        calendar.add(Calendar.MINUTE, -59);
        calendar.add(Calendar.SECOND, -59);
        Date date = calendar.getTime();
        String dateFrom=(format.format(date));

        List<String> enabledCommunitiesList = null;
        if (enabledCommunities != null && !enabledCommunities.isEmpty()) {
            enabledCommunitiesList = Arrays.asList(enabledCommunities.split("\\s*,\\s*"));
        }

        logger.debug("Sending emails for community claims between " + dateFrom + " and "+dateTo);

        try {
            // Get all claims between dateFrom and dateTo which satisfy source_type == "context" or target_type == "context"
            claims = fetchClaimHandler.fetchClaimsByDate(dateFrom, dateTo, null, null, "", "source", true, types, false);
        } catch (SQLStoreException|Exception e) {
            logger.error("Could not fetch claims by date from "+dateFrom+" to "+dateTo, e);
        }
        logger.debug(claims.size() + " new claims found");
        for (Claim claim: claims) {
            if (claim.getSourceType().equals("context")) {
                context = (Context)claim.getSource();
            } else {
                context = (Context)claim.getTarget();
            }

            String openaireId = context.getOpenaireId().split("::")[0];
            if (!managersOfCommunity.containsKey(openaireId) && (enabledCommunitiesList == null || enabledCommunitiesList.contains(openaireId))) {

                /*
                // specialRecipients are used currently for testing purposes
                List<String> tmpManagers = null;
                if (specialRecipients != null && !specialRecipients.isEmpty()) {
                    tmpManagers = Arrays.asList(specialRecipients.split("\\s*,\\s*"));
                    logger.debug("Special recipients: " + specialRecipients);
                    managersOfCommunity.put(openaireId, tmpManagers);
                }
                */


                // Send emails to actual project managers instead of special recipients
                List<String> managers = null;
                try {
                    //CommunityUtils communityInfo = this.communityUtils.getCommunityInfo(openaireId);
                    List<ManagerUtils> managerUtils = this.managerUtils.getManagersByEmail(openaireId);
                    //managers = communityInfo.getManagers();
                    if(managerUtils != null) {
                        for (ManagerUtils manager:managerUtils) {
                            String managerMail = manager.getEmail();
                            Notification notification = fetchNotificationHandler.fetchNotification(openaireId, managerMail);
                            if (notification == null)  {
                                if(managers == null) {
                                    managers = new ArrayList<>();
                                }
                                managers.add(managerMail);
                                logger.debug("Sending email to community manager: "+ managerMail);
                            }
                        }
                    }
                    else {
                        logger.debug("Community Managers: null");
                    }
                } catch (Exception e) {
                    logger.error(e);
                    e.printStackTrace();
                } catch (SQLStoreException e) {
                    e.printStackTrace();
                    logger.error(e);
                }

                logger.debug("Managers of community " + openaireId + ": "+managers);
                managersOfCommunity.put(openaireId, managers);


                if (managersOfCommunity.get(openaireId) != null &&
                        !managersOfCommunity.get(openaireId).isEmpty()) {
                    send(openaireId, context.getTitle().split(">")[0], "community", managersOfCommunity.get(openaireId));
                }
            } else if(enabledCommunitiesList != null && !enabledCommunitiesList.contains(openaireId)) {
                logger.debug("Community "+openaireId+" is not enabled");
            }
        }
    }

    public void notificationEmails_For_ProjectClaims(String dateTo, SimpleDateFormat format) {
        Project project = null;
        List<String> types = new ArrayList<String>();

        types.add(ClaimUtils.PROJECT);

        logger.debug("Sending email for project claims from notification table");

        try {
            List<Notification> trueNotifications = fetchNotificationHandler.fetchTrueNotifications();
            logger.debug(trueNotifications);
            if(trueNotifications != null) {
                for(Notification notification : trueNotifications) {
                    List<String> managers = fetchProjectHandler.fetchContactEmailsByProjectId(notification.getOpenaireId());
                    if(managers != null && managers.contains(notification.getUserMail())) {

                        Date _dateTo = format.parse(dateTo);

                        Date _last_interaction_date = notification.getDate();
                        Calendar cal = Calendar.getInstance();
                        cal.setTime(_last_interaction_date);
                        cal.add(Calendar.SECOND, 1);
                        _last_interaction_date = cal.getTime();

                        String last_interaction_date = (format.format(_last_interaction_date));

                        long diff = _dateTo.getTime() - notification.getDate().getTime();
                        diff = diff / 1000;

                        if (diff >= (notification.getFrequency() * 3600)) {
                            if (fetchClaimHandler.fetchNumberOfClaimsByDateAndOpenaireId(last_interaction_date, dateTo, notification.getOpenaireId(), null, null, "", null, true, types, false) > 0) {
                                List<String> managersByNotification = new ArrayList<>();
                                managersByNotification.add(notification.getUserMail());

                                project = fetchProjectHandler.fetchProjectById(notification.getOpenaireId());

                                logger.debug("Sending email for project claims between " + last_interaction_date + " and " + dateTo + " to " + notification.getUserMail());

                                send(project.getOpenaireId(), project.getName(), "project", managersByNotification);
                            }

                            notificationHandler.updateNotificationLastInteractionDate(notification.getOpenaireId(), notification.getUserMail(), _dateTo);
                        }
                    } else {
                        logger.debug("managers do not contain "+notification.getUserMail());
                    }
                }
            } else {
                logger.debug("true notifications: null");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } catch (SQLStoreException e) {
            e.printStackTrace();
        }
    }

    public void notificationEmails_For_CommunityClaims(String dateTo, SimpleDateFormat format) {
        Context context = null;
        List<String> types = new ArrayList<String>();

        types.add(ClaimUtils.CONTEXT);

        List<String> enabledCommunitiesList = null;
        if (enabledCommunities != null && !enabledCommunities.isEmpty()) {
            enabledCommunitiesList = Arrays.asList(enabledCommunities.split("\\s*,\\s*"));
        }

        logger.debug("Sending email for community claims from notification table");

        try {
            List<Notification> trueNotifications = fetchNotificationHandler.fetchTrueNotifications();

            Map<String, List<String>> allManagers = new HashMap<>();

            if(trueNotifications != null) {
                for(Notification notification : trueNotifications) {
                    if (enabledCommunitiesList == null || enabledCommunitiesList.contains(notification.getOpenaireId())) {

                        if(!allManagers.containsKey(notification.getOpenaireId())) {
                            allManagers.put(notification.getOpenaireId(), new ArrayList());

//                        CommunityUtils communityInfo = this.communityUtils.getCommunityInfo(notification.getOpenaireId());
                            List<ManagerUtils> managerUtils = this.managerUtils.getManagersByEmail(notification.getOpenaireId());

//                        List<String> managers = null;
                            if (managerUtils != null) {
                                for (ManagerUtils manager : managerUtils) {
                                    allManagers.get(notification.getOpenaireId()).add(manager.getEmail());
                                }
                            }
                        }


//                        if (managers != null && managers.contains(notification.getUserMail())) {
                        if(allManagers.get(notification.getOpenaireId()).contains(notification.getUserMail())) {
                            Date _dateTo = format.parse(dateTo);

                            Date _last_interaction_date = notification.getDate();
                            Calendar cal = Calendar.getInstance();
                            cal.setTime(_last_interaction_date);
                            cal.add(Calendar.SECOND, 1);
                            _last_interaction_date = cal.getTime();

                            String last_interaction_date = (format.format(_last_interaction_date));
                            long diff = _dateTo.getTime() - notification.getDate().getTime();
                            diff = diff / 1000;

                            if (diff >= (notification.getFrequency() * 3600)) {
                                if (fetchClaimHandler.fetchNumberOfClaimsByDateAndOpenaireId(last_interaction_date, dateTo, notification.getOpenaireId(), null, null, "", null, true, types, false) > 0) {
                                    List<String> managersByNotification = new ArrayList<>();
                                    managersByNotification.add(notification.getUserMail());

                                    // We need that to get name of community
                                    context = fetchContextHandler.fetchFirstContextByCommunityId(notification.getOpenaireId());

                                    logger.debug("Sending email for community claims between " + last_interaction_date + " and " + dateTo + " to " + notification.getUserMail());

                                    send(context.getOpenaireId().split("::")[0], context.getTitle().split(">")[0], "community", managersByNotification);
                                }

                                notificationHandler.updateNotificationLastInteractionDate(notification.getOpenaireId(), notification.getUserMail(), _dateTo);
                            } else {
                                logger.debug("[no notification] User "+notification.getUserMail()+" got latest notification at "+last_interaction_date+" and has set frequency to: "+notification.getFrequency());
                            }
                        } else {
                            logger.debug("User "+notification.getUserMail()+" is not manager of community "+notification.getOpenaireId());
                        }
                    } else {
                        logger.debug("Community "+notification.getOpenaireId()+" is not enabled");
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } catch (SQLStoreException e) {
            e.printStackTrace();
        }
    }

    public void send(String openaire_id, String openaire_name, String type, List<String> managers) {
        logger.debug("Sending email");
        String openaireClaimsPageUrl = "";
        String manageUserNotificationsPage = "";
        String messageContent = "";
        String subject = "";

        if(type.equals("project")) {
            openaireClaimsPageUrl = openaireProjectClaimsPage + openaire_id;

            subject = "[OpenAIRE] Links notification";
            messageContent = "There are new Claims for: '" + openaire_name +"' project for which you seem to be a contact person." +
                    "<br>Click <a href=\""+openaireClaimsPageUrl+"\">here</a> to curate these Claims.";
        } else if(type.equals("community")) {
            openaireClaimsPageUrl = openaireCommunityClaimsPage.replace("{community}", openaire_id);

            manageUserNotificationsPage = manageCommunityUserNotificationsPage.replace("{community}", openaire_id);

            subject = "[OpenAIRE-Connect] "+openaire_name+": Links notification";
            messageContent =
                    " <div style=\"font-size:14px;\">" +
                    "    <p>" +
                    "       There are new links for '"+openaire_name+"' community. Click  <a href=\""+openaireClaimsPageUrl+"\">here</a> to view the links.\n" +
                    "    </p>" +
                    "    <p>OpenAIRE team<br/>" +
                    "       <a href=\"https://www.openaire.eu\">www.openaire.eu</a>" +
                    "    </p>" +
                    "    <p style=\"font-size:11px;\">You are receiving this e-mail as manager of the community  <a href=\"https://"+openaire_id+".openaire.eu\">"+openaire_name+"</a>." +
                    "    If you are not responsible for this community, please <a href=\"mailto:"+contactMail+"\">contact us</a>." +
                    "    <br/>" +
                    "    Click  <a href=\""+manageUserNotificationsPage+"\">here</a> to manage your notification settings. </p>" +
                    "    </div>"
                    ;
        }

        //logger.debug(messageContent);

        // Get system properties
        Properties properties = System.getProperties();
        properties.setProperty("mail.smtp.host", host);
        properties.put("mail.smtp.port", port);
        properties.put("mail.smtp.auth", auth); //enable authentication
        properties.put("mail.smtp.starttls.enable", "true");
        if(sslProtocols != null) {
            properties.put("mail.smtp.ssl.protocols", sslProtocols);
        }
        Session session = Session.getInstance(properties,
            new javax.mail.Authenticator() {
                protected PasswordAuthentication getPasswordAuthentication() {
                    return new PasswordAuthentication(username, password);
                }
            });

        try {
            // Create a default MimeMessage object.
            MimeMessage message = new MimeMessage(session);

            // Set From: header field of the header.
            message.setFrom(new InternetAddress(from));

            // Set To: header field of the header.
            for(String to : managers) {
                logger.debug(to);
                message.addRecipient(Message.RecipientType.TO, new InternetAddress(to));
            }
            message.addRecipient(Message.RecipientType.BCC, new InternetAddress("openaire.test@gmail.com"));

            // Set Subject: header field
            message.setSubject(subject);

            // For simple text setText() can be used instead of setContent()

            // Send the actual HTML message, as big as you like
            message.setContent(messageContent, "text/html");

            // Send message
            Transport.send(message);
            logger.debug("Sent message successfully....\n");

        } catch (AddressException ae) {
            logger.error("Email could not be send.", ae);

        } catch (MessagingException me) {
            logger.error("Email could not be send.", me);
        }
    }

    public void setFetchClaimHandler(FetchClaimHandler fetchClaimHandler) {
        this.fetchClaimHandler = fetchClaimHandler;
    }

    public void setFetchProjectHandler(FetchProjectHandler fetchProjectHandler) {
        this.fetchProjectHandler = fetchProjectHandler;
    }

    public void setFetchNotificationHandler(FetchNotificationHandler fetchNotificationHandler) {
        this.fetchNotificationHandler = fetchNotificationHandler;
    }

    public void setNotificationHandler(NotificationHandler notificationHandler) {
        this.notificationHandler = notificationHandler;
    }

    public void setFetchContextHandler(FetchContextHandler fetchContextHandler) {
        this.fetchContextHandler = fetchContextHandler;
    }

    public void setOpenaireProjectClaimsPage(String openaireProjectClaimsPage) {
        EmailSender.openaireProjectClaimsPage = openaireProjectClaimsPage;
    }

    public void setOpenaireCommunityClaimsPage(String openaireCommunityClaimsPage) {
        EmailSender.openaireCommunityClaimsPage = openaireCommunityClaimsPage;
    }

    public void setManageCommunityUserNotificationsPage(String manageCommunityUserNotificationsPage) {
        EmailSender.manageCommunityUserNotificationsPage = manageCommunityUserNotificationsPage;
    }

    public void setUsername(String username) {
        EmailSender.username = username;
    }

    public void setPassword(String password) {
        EmailSender.password = password;
    }

    public void setHost(String host) {
        EmailSender.host = host;
    }

    public void setPort(String port) {
        EmailSender.port = port;
    }

    public void setFrom(String from) {
        EmailSender.from = from;
    }

    public void setAuth(String auth) {
        EmailSender.auth = auth;
    }

    public void setContactMail(String contactMail) { EmailSender.contactMail = contactMail; }

    public void setSpecialRecipients(String specialRecipients) {
        EmailSender.specialRecipients = specialRecipients;
    }

    public void setDefaultFrequencyInHours(String defaultFrequencyInHours) {
        this.defaultFrequencyInHours = defaultFrequencyInHours;
    }

    public void setEnabledCommunities(String enabledCommunities) {
        EmailSender.enabledCommunities = enabledCommunities;
    }

    public static boolean isNotifyCommunityManagers() {
        return notifyCommunityManagers;
    }

    public static void setNotifyCommunityManagers(boolean notifyCommunityManagers) {
        EmailSender.notifyCommunityManagers = notifyCommunityManagers;
    }

    public static boolean isNotifyProjectManagers() {
        return notifyProjectManagers;
    }

    public static void setNotifyProjectManagers(boolean notifyProjectManagers) {
        EmailSender.notifyProjectManagers = notifyProjectManagers;
    }

//    public void setCommunityUtils(CommunityUtils communityUtils) {
//        this.communityUtils = communityUtils;
//    }
//
//    public CommunityUtils getCommunityUtils() {
//        return communityUtils;
//    }


    public ManagerUtils getManagerUtils() {
        return managerUtils;
    }

    public void setManagerUtils(ManagerUtils managerUtils) {
        this.managerUtils = managerUtils;
    }

    public static String getSslProtocols() {
        return sslProtocols;
    }

    public static void setSslProtocols(String sslProtocols) {
        EmailSender.sslProtocols = sslProtocols;
    }
}
