/*
 * Copyright 2006-2021 DLR, Germany
 * 
 * SPDX-License-Identifier: EPL-1.0
 * 
 * https://rcenvironment.de/
 */

package de.rcenvironment.core.mail.internal;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import java.io.IOException;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

import javax.mail.MessagingException;

import org.easymock.EasyMock;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;

import com.icegreen.greenmail.util.GreenMail;
import com.icegreen.greenmail.util.ServerSetupTest;

import de.rcenvironment.core.configuration.bootstrap.RuntimeDetection;
import de.rcenvironment.core.mail.InvalidMailException;
import de.rcenvironment.core.mail.MailDispatchResult;
import de.rcenvironment.core.mail.MailDispatchResultListener;

/**
 * Test case for the class {@link MailServiceImpl}.
 * 
 * @author Tobias Rodehutskors
 */
public class MailServiceImplTest extends AbstractMailServiceImplTest {

    private static final String GREENMAIL_IMPLICIT_ENCRYPTION_JSON = "/greenmail_implicit_encryption.json";

    private static final String NOT_IMPLEMENTED_YET = "Not implemented yet";

    private static final int TWO_SECS_IN_MILLIS = 2000;

    /**
     * Common test setup.
     */
    @Before
    public void setUp() {
        RuntimeDetection.allowSimulatedServiceActivation();
    }

    /**
     * Tests if the mail dispatch fails with an incomplete configuration, which only specifies the host and port but nothing else.
     * 
     * @throws InterruptedException unexpected
     * @throws ExecutionException unexpected
     * @throws IOException unexpected
     * @throws InvalidMailException unexpected
     */
    @Test
    public void testSendMailWithInvalidConfig() throws InterruptedException, ExecutionException, IOException, InvalidMailException {

        boolean configured = setupMailService("/invalidConfig.json");
        assertFalse(configured);

        Future<?> sendMail = mailServiceImpl.sendMail(createDefaultMail(), new MailDispatchResultListener() {

            @Override
            public void receiveResult(MailDispatchResult result, String message) {
                assertEquals(MailDispatchResult.FAILURE_MAIL_SERVICE_NOT_CONFIGURED, result);
                assertEquals(null, message);
            }
        });
        sendMail.get();
    }

    /**
     * This test will attempt to establish a connection with explicit security using the STARTTLS command. However, the server does not
     * support STARTTLS. Therefore, we expect a permanent failure.
     * 
     * @throws IOException unexpected
     * @throws InterruptedException unexpected
     * @throws ExecutionException unexpected
     * @throws InvalidMailException unexpected
     */
    @Test
    public void testConnectWithExplicitSecurityButServerDoesNotSupportStartTLS()
        throws IOException, InterruptedException, ExecutionException, InvalidMailException {

        GreenMail greenmail = new GreenMail(ServerSetupTest.SMTP); // port 3025
        greenmail.start();

        boolean configured = setupMailService("/greenmail_explicit_encryption.json");
        assertTrue(configured);

        final Future<?> sendMail = mailServiceImpl.sendMail(createDefaultMail(), new MailDispatchResultListener() {

            @Override
            public void receiveResult(MailDispatchResult result, String message) {
                assertEquals(MailDispatchResult.FAILURE, result);
                assertTrue(message.contains("STARTTLS is required but host does not support STARTTLS"));
            }
        });
        sendMail.get();

        greenmail.stop();
    }

    /**
     * This test will attempt to establish a connection with implicit security to a SMTP port. Therefore, we expect a permanent failure.
     * 
     * @throws IOException unexpected
     * @throws InterruptedException unexpected
     * @throws ExecutionException unexpected
     * @throws InvalidMailException unexpected
     */
    @Test
    public void testConnectWithImplicitSecurityButServerExpectsExplicitSecurity() throws IOException, InvalidMailException,
        InterruptedException, ExecutionException {

        GreenMail greenmail = new GreenMail(ServerSetupTest.SMTP); // port 3025
        greenmail.start();

        boolean configured = setupMailService("/greenmail_implicit_encryption_port3025.json");
        assertTrue(configured);

        final Future<?> sendMail = mailServiceImpl.sendMail(createDefaultMail(), new MailDispatchResultListener() {

            @Override
            public void receiveResult(MailDispatchResult result, String message) {
                assertEquals(MailDispatchResult.FAILURE, result);
                assertTrue(message.contains("Could not connect to SMTP host"));
            }
        });
        sendMail.get();

        greenmail.stop();
    }

    /**
     * Attempts to connect to the Greenmail dummy server over an implicitly secured connection.
     * 
     * @throws IOException unexpected
     * @throws InterruptedException unexpected
     * @throws ExecutionException unexpected
     * @throws InvalidMailException unexpected
     * @throws MessagingException unexpected
     */
    @Ignore
    @Test
    public void testConnectWithImplicitSecurity()
        throws IOException, InterruptedException, ExecutionException, InvalidMailException, MessagingException {

        // opens an SMTPS server on port 3465 which awaits SSL/TLS connections
        GreenMail greenmail = new GreenMail(ServerSetupTest.SMTPS);
        greenmail.getManagers().getUserManager().setAuthRequired(true);
        greenmail.start();

        boolean configured = setupMailService(GREENMAIL_IMPLICIT_ENCRYPTION_JSON);
        assertTrue(configured);

        mailServiceImpl.trustAllCertificates();
        assertTrue(mailServiceImpl.canConnectToServer());

        greenmail.stop();
    }

    /**
     * Attempts to connect to the Greenmail dummy server with an invalid certificate over an implicitly secured connection.
     * 
     * @throws IOException unexpected
     * @throws InterruptedException unexpected
     * @throws ExecutionException unexpected
     * @throws InvalidMailException unexpected
     * @throws MessagingException unexpected
     */
    @Ignore
    @Test
    public void testConnectWithImplicitSecurityAndInvalidCertificate()
        throws IOException, InterruptedException, ExecutionException, InvalidMailException, MessagingException {

        // opens an SMTPS server on port 3465 which awaits SSL/TLS connections
        GreenMail greenmail = new GreenMail(ServerSetupTest.SMTPS);
        greenmail.getManagers().getUserManager().setAuthRequired(true);
        greenmail.start();

        boolean configured = setupMailService(GREENMAIL_IMPLICIT_ENCRYPTION_JSON);
        assertTrue(configured);

        assertFalse(mailServiceImpl.canConnectToServer());

        greenmail.stop();
    }

    /**
     * Attempts to send a mail to the Greenmail dummy server over an implicitly secured connection.
     * 
     * @throws IOException unexpected
     * @throws InvalidMailException unexpected
     * @throws ExecutionException unexpected
     * @throws InterruptedException unexpected
     * @throws MessagingException unexpected
     */
    @Ignore
    @Test
    public void testMailDispatchWithImplicitSecurity() throws IOException, InvalidMailException, InterruptedException, ExecutionException,
        MessagingException {

        // opens an SMTPS server on port 3465 which awaits SSL/TLS connections
        GreenMail greenmail = new GreenMail(ServerSetupTest.SMTPS);
        greenmail.getManagers().getUserManager().setAuthRequired(true);
        greenmail.start();

        boolean configured = setupMailService(GREENMAIL_IMPLICIT_ENCRYPTION_JSON);
        // Since Greenmail uses self-signed certificates, we need to disable certificate validation.
        mailServiceImpl.trustAllCertificates();
        assertTrue(configured);

        Future<?> sendMail = mailServiceImpl.sendMail(createDefaultMail(), new MailDispatchResultListener() {

            @Override
            public void receiveResult(MailDispatchResult result, String message) {
                assertEquals(MailDispatchResult.SUCCESS, result);
                assertEquals(null, message);
            }
        });
        // wait till the mail is send
        sendMail.get();

        validateDefaultMail(greenmail.getReceivedMessages());

        greenmail.stop();
    }

    /**
     * Tests if the mail delivery is retried after an initial connection failure.
     * 
     * @throws IOException unexpected
     * @throws InvalidMailException unexpected
     * @throws InterruptedException unexpected
     * @throws ExecutionException unexpected
     * @throws MessagingException unexpected
     */
    @Ignore
    @Test
    public void testMailDispatchIsReattempted() throws IOException, InvalidMailException, InterruptedException, ExecutionException,
        MessagingException {

        // opens an SMTPS server on port 3465 which awaits SSL/TLS connections
        final GreenMail greenmail = new GreenMail(ServerSetupTest.SMTPS);
        greenmail.getManagers().getUserManager().setAuthRequired(true);
        // do not start the server here

        boolean configured = setupMailService(GREENMAIL_IMPLICIT_ENCRYPTION_JSON);
        // Since Greenmail uses self-signed certificates, we need to disable certificate validation.
        mailServiceImpl.trustAllCertificates();
        assertTrue(configured);

        Future<?> sendMail = mailServiceImpl.sendMail(createDefaultMail(), new MailDispatchResultListener() {

            private boolean firstRun = true;

            @Override
            public void receiveResult(MailDispatchResult result, String message) {
                // since the mail server is not running, we expect a failure
                if (firstRun) {
                    assertEquals(MailDispatchResult.FAILURE_RETRY, result);
                    assertEquals(null, message);
                    // activate the server
                    greenmail.start();
                    firstRun = false;
                } else {
                    // we expect a retry and a successful mail delivery after the start of the server
                    assertEquals(MailDispatchResult.SUCCESS, result);
                    assertEquals(null, message);
                }

            }
        });
        // wait till the mail is send
        sendMail.get();

        validateDefaultMail(greenmail.getReceivedMessages());

        greenmail.stop();
    }

    /**
     * Tests if the mail delivery can be canceled while a retry is attempted.
     * 
     * @throws IOException unexpected
     * @throws InvalidMailException unexpected
     * @throws InterruptedException unexpected
     * @throws ExecutionException unexpected
     */
    @Ignore
    @Test
    public void testCancelFutureWhileMailDispatchIsReattempted() throws IOException, InvalidMailException, InterruptedException,
        ExecutionException {
        // Do not start a Greenmail server, since we want to check the behavior during a retry

        boolean configured = setupMailService(GREENMAIL_IMPLICIT_ENCRYPTION_JSON);
        assertTrue(configured);

        MailDispatchResultListener listenerMock = EasyMock.createStrictMock(MailDispatchResultListener.class);
        listenerMock.receiveResult(MailDispatchResult.FAILURE_RETRY, null);
        EasyMock.expectLastCall().once();
        listenerMock.receiveResult(EasyMock.eq(MailDispatchResult.FAILURE), EasyMock.contains("interrupted"));
        EasyMock.expectLastCall().once();
        EasyMock.replay(listenerMock);

        Future<?> sendMail = mailServiceImpl.sendMail(createDefaultMail(), listenerMock);

        Thread.sleep(TWO_SECS_IN_MILLIS);

        boolean canceled = sendMail.cancel(true);
        assertTrue(canceled);

        expectedException.expect(CancellationException.class);
        sendMail.get();
    }

    /**
     * This test will attempt to establish a connection with explicit security using the STARTTLS command.
     * 
     * TODO Greenmail currently does not support STARTTLS at all. Therefore, this test should be created as soon as Greenmail supports
     * STARTTLS.
     */
    @Ignore
    public void testConnectWithExplicitSecurity() {
        throw new UnsupportedOperationException(NOT_IMPLEMENTED_YET);
    }

    /**
     * This test will attempt to establish a connection and authenticate with invalid credentials.
     * 
     * TODO Greenmail 1.5.1 does not verify users, but will accept any password. However, future version probably will support this feature:
     * https://github.com/greenmail-mail-test/greenmail/issues/127 . It would be nice to have an integration test that checks how our code
     * behaves if the wrong user credentials are used when connecting to the server.
     */
    @Ignore
    public void testConnectWithInvalidCredentials() {
        throw new UnsupportedOperationException(NOT_IMPLEMENTED_YET);
    }

    /**
     * Tests the mail service deactivation during waiting for reattempt of mail delivery.
     * 
     * TODO
     */
    @Ignore
    public void testDeactivateMailService() {
        throw new UnsupportedOperationException(NOT_IMPLEMENTED_YET);
    }
}
