package io.jstack.sendcloud4j.mail;

import io.jstack.sendcloud4j.SendCloud;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.client.fluent.Form;
import org.apache.http.client.fluent.Request;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.mime.HttpMultipartMode;
import org.apache.http.entity.mime.MultipartEntityBuilder;

import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Map;

/**
 * 邮件 API v2 实现。
 *
 * @author denger
 */
public class MailWebApi {

    private SendCloud sendCloud;


    private static final int DEFAULT_CONNECT_TIMEOUT = 500;
    private static final int DEFAULT_SOCKET_TIMEOUT = 1000;

    private int connectTimeout;
    private int socketTimeout;
    private HttpHost proxy;

    private static Charset UTF_8 = Charset.forName("UTF-8");

    public static final ContentType TEXT_PLAIN = ContentType.create("text/plain", UTF_8);


    public static MailWebApi create(SendCloud sendCloud) {
        return new MailWebApi(sendCloud);
    }

    private MailWebApi(SendCloud sendCloud) {
        this.socketTimeout = DEFAULT_SOCKET_TIMEOUT;
        this.connectTimeout = DEFAULT_CONNECT_TIMEOUT;
        this.sendCloud = sendCloud;
    }

    public MailWebApi viaProxy(String proxy) {
        this.proxy = HttpHost.create(proxy);
        return this;
    }

    public MailWebApi viaProxy(HttpHost proxy) {
        this.proxy = proxy;
        return this;
    }

    public MailWebApi socketTimeout(int socketTimeout) {
        this.socketTimeout = socketTimeout;
        return this;
    }
    public MailWebApi connectTimeout(int connectTimeout) {
        this.connectTimeout = connectTimeout;
        return this;
    }

    public Result send(Email email) {
        try {
            String jsonResult = requestSend(getSendAPIURI(email), email);
            return new Result(jsonResult);
        } catch (IOException ioe) {
            return Result.createExceptionResult(email, ioe);
        }
    }

    protected String getSendAPIURI(Email email) {
        return String.format("%s/apiv2/mail/%s", SendCloud.API_DOMAIN,
                (isTemplate(email) ? "sendtemplate" : "send"));
    }

    private boolean isTemplate(Email email) {
        return (email instanceof TemplateEmail);
    }

    private String requestSend(String uri, Email email) throws IOException {
        Request request = Request.Post(uri)
                .connectTimeout(connectTimeout)
                .socketTimeout(socketTimeout);
        if (proxy != null) {
            request.viaProxy(proxy);
        }
        if (email.hasAttachment()) {
            request.body(getMultipartEmailHttpEntity(email));
        } else {
            request.bodyForm(convertFrom(email.getParameters()).build(), UTF_8);
        }
        return request.execute().returnContent().asString(UTF_8);
    }

    private HttpEntity getMultipartEmailHttpEntity(Email email) {
        MultipartEntityBuilder entityBuilder = createEntityBuilder();

        Map<String, byte[]> attachments = email.attachments();
        for (Map.Entry<String, byte[]> attachment : attachments.entrySet()) {
            entityBuilder.addBinaryBody(email.attachmentsKey(), attachment.getValue(),
                    ContentType.MULTIPART_FORM_DATA, attachment.getKey());
        }
        addParametersToTextBody(entityBuilder, email.getParameters());

        return entityBuilder.build();
    }

    public MultipartEntityBuilder createEntityBuilder() {
        return MultipartEntityBuilder.create().setMode(HttpMultipartMode.BROWSER_COMPATIBLE).setCharset(UTF_8);
    }

    private void addParametersToTextBody(MultipartEntityBuilder entityBuilder, Map<String, String> parameters) {
        for (Map.Entry<String, String> param : parameters.entrySet()) {
            entityBuilder.addTextBody(param.getKey(), param.getValue(), TEXT_PLAIN);
        }
        entityBuilder.addTextBody("apiUser", sendCloud.apiUser(), TEXT_PLAIN);
        entityBuilder.addTextBody("apiKey", sendCloud.apiKey(), TEXT_PLAIN);
    }

    private Form convertFrom(Map<String, String> parameters) {
        Form form = Form.form();
        for (Map.Entry<String, String> param : parameters.entrySet()) {
            form.add(param.getKey(), param.getValue());
        }
        form.add("apiUser", sendCloud.apiUser());
        form.add("apiKey", sendCloud.apiKey());

        return form;
    }

}