package com.alexanderlogistics;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Map;
import java.util.Properties;

import javax.mail.Folder;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Store;

import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicHeader;
import org.junit.Test;

import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sun.mail.imap.IMAPFolder;

import junit.framework.Assert;


/**
 * This test shows how to access outlook via oauth2
 * 
 * See: https://www.java-forum.org/thema/javamail-mit-oauth2-an-o365.199133/
 * 
 * @author rsoika
 * 
 */
public class TestIMAPOutlookOAuth {

    private static final String TENANT_ID = "51e2f038-0a96-41be-801d-bb4118aa018e";
    private static final String CLIENT_ID = "39e9eec5-6939-47dc-9729-3438a9898e63";
    private static final String CLIENT_SECRET = "A6x8Q~EWVUyt~gfyTHtIJf4ig.F9tajUpEEXMaCL";

    /*
     * Neue Daten
     * 
     * Wert: zDL8Q~iEy8AX~dEmIOHrVv3aBTCZLrHIFmShibZ_
     * Geheime ID: bcaa558a-3300-4498-94e1-2267f418afd5
     */

    @Test
    public void TestMailboxImixs() {
        Store store = null;
        String token = null;
        try {
            // try to connect...
            token = getAuthToken();
            Assert.assertNotNull(token);
            store = connect(token, "imixs@alexander-logistics.com");
            Assert.assertNotNull(store);

            if (!store.isConnected()) {
                // not connected!
            } else {
                // read inbox....
                IMAPFolder inbox = (IMAPFolder) store.getFolder("INBOX");
                inbox.open(Folder.READ_WRITE);
                // fetches all messages from the INBOX...
                Message[] messages = inbox.getMessages();
                System.out.println("..." + messages.length + " new messages found");
            }
        } catch (IOException e) {
            e.printStackTrace();
            Assert.fail();
        } catch (MessagingException e) {
            e.printStackTrace();
            Assert.fail();
        }

    }

    @Test
    public void TestMailboxImixsBHV() {
        Store store = null;
        String token = null;
        try {
            // try to connect...
            token = getAuthToken();
            Assert.assertNotNull(token);
            store = connect(token, "Imixs_bhv@alexander-logistics.com");
            // store = connect(token, "mgeisler@alexander-logistics.com");

            Assert.assertNotNull(store);

            if (!store.isConnected()) {
                // not connected!
            } else {
                // read inbox....
                IMAPFolder inbox = (IMAPFolder) store.getFolder("INBOX");
                inbox.open(Folder.READ_WRITE);
                // fetches all messages from the INBOX...
                Message[] messages = inbox.getMessages();
                System.out.println("..." + messages.length + " new messages found");
            }
        } catch (IOException e) {
            e.printStackTrace();
            Assert.fail();
        } catch (MessagingException e) {
            e.printStackTrace();
            Assert.fail();
        }

    }

    /**
     * This method returns a connection to an javax.mail.store
     */
    public Store connect(String oauth2AccessToken, String userEmailId) {

        try {

            String host = "outlook.office365.com";
            String port = "993";
            Store store = null;

            String SSL_FACTORY = "javax.net.ssl.SSLSocketFactory";
            Properties props = new Properties();

            props.put("mail.imaps.ssl.enable", "true");
            props.put("mail.imaps.sasl.enable", "true");
            props.put("mail.imaps.port", port);
            props.put("mail.imaps.auth.mechanisms", "XOAUTH2");
            props.put("mail.imaps.sasl.mechanisms", "XOAUTH2");

            props.put("mail.imaps.auth.login.disable", "true");
            props.put("mail.imaps.auth.plain.disable", "true");

            props.setProperty("mail.imaps.socketFactory.class", SSL_FACTORY);
            props.setProperty("mail.imaps.socketFactory.fallback", "false");
            props.setProperty("mail.imaps.socketFactory.port", port);
            props.setProperty("mail.imaps.starttls.enable", "true");

            // optional set imap user?
            // props.put("mail.imap.user", userEmailId);

            props.put("mail.imaps.auth.plain.disable", "true");
            props.put("mail.debug", "true");
            props.put("mail.debug.auth", "true");

            Session session = Session.getInstance(props);
            session.setDebug(true);

            store = session.getStore("imaps");

            System.out.println("OAUTH2 IMAP trying to connect with system properties to Host:" + host + ", Port: "
                    + port + ", userEmailId: " + userEmailId + ", AccessToken: " + oauth2AccessToken);

            store.connect(host, userEmailId, oauth2AccessToken);
            if (store.isConnected()) {
                System.out.println("Connection Established using imap protocol successfully !");
                return store;

            }
            System.out.println("Store.Connect failed!");
        } catch (Exception e) {
            System.out.println("Store.Connect failed with the errror: " + e.getMessage());
            StringWriter sw = new StringWriter();
            e.printStackTrace(new PrintWriter(sw));
            String exceptionAsString = sw.toString();
            System.out.println(exceptionAsString);
            Assert.fail();
        }

        return null;

    }

    /**
     * Helper method to receive a Microsoft access Token
     * 
     * https://jwt.ms/
     * 
     * "OAuth2": {
     * 
     * "Authority":
     * https://login.microsoftonline.com/51e2f038-0a96-41be-801d-bb4118aa018e/v2.0,
     * 
     * "ClientId": "39e9eec5-6939-47dc-9729-3438a9898e63",
     * 
     * "ClientSecret": "A6x8Q~EWVUyt~gfyTHtIJf4ig.F9tajUpEEXMaCL"
     * 
     * }
     * 
     * 
     * @return
     * @throws IOException
     * @throws ClientProtocolException
     */
    public String getAuthToken() throws ClientProtocolException, IOException {
        CloseableHttpClient client = HttpClients.createDefault();

        HttpPost loginPost = new HttpPost("https://login.microsoftonline.com/" + TENANT_ID + "/oauth2/v2.0/token");
        String scopes = "https://outlook.office365.com/.default";
        // scopes="https://outlook.office365.com/IMAP.AccessAsUser.All";
        // scopes="https://graph.microsoft.com/.default";
        // scopes="openid profile offline_access https://outlook.office365.com/SMTP.Send
        // https://outlook.office365.com/POP.AccessAsUser.All
        // https://outlook.office365.com/IMAP.AccessAsUser.All";

        String encodedBody = "client_id=" + CLIENT_ID + "&scope=" + scopes + "&client_secret=" + CLIENT_SECRET
                + "&grant_type=client_credentials";

        loginPost.setEntity(new StringEntity(encodedBody, ContentType.APPLICATION_FORM_URLENCODED));

        loginPost.addHeader(new BasicHeader("cache-control", "no-cache"));
        CloseableHttpResponse loginResponse = client.execute(loginPost);

        InputStream inputStream = loginResponse.getEntity().getContent();
        byte[] response = readAllBytes(inputStream);
        ObjectMapper objectMapper = new ObjectMapper();
        JavaType type = objectMapper.constructType(
                objectMapper.getTypeFactory().constructParametricType(Map.class, String.class, String.class));
        Map<String, String> parsed = new ObjectMapper().readValue(response, type);
        String result = parsed.get("access_token");

        System.out.println("....access token = " + result);
        return result;
    }

    /**
     * Helper method to read bytes from strem
     * 
     * @param inputStream
     * @return
     * @throws IOException
     */
    private static byte[] readAllBytes(InputStream inputStream) throws IOException {
        final int bufLen = 4 * 0x400; // 4KB
        byte[] buf = new byte[bufLen];
        int readLen;
        IOException exception = null;

        try {
            try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
                while ((readLen = inputStream.read(buf, 0, bufLen)) != -1)
                    outputStream.write(buf, 0, readLen);

                return outputStream.toByteArray();
            }
        } catch (IOException e) {
            exception = e;
            throw e;
        } finally {
            if (exception == null)
                inputStream.close();
            else
                try {
                    inputStream.close();
                } catch (IOException e) {
                    exception.addSuppressed(e);
                }
        }
    }
}