package org.jetlinks.community.notify.email.embedded;

import com.alibaba.fastjson.JSONObject;
import io.vavr.control.Try;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.hswebframework.web.exception.BusinessException;
import org.hswebframework.web.id.IDGenerator;
import org.hswebframework.web.utils.ExpressionUtils;
import org.hswebframework.web.validator.ValidatorUtils;
import org.jetlinks.community.notify.*;
import org.jetlinks.community.notify.email.EmailProvider;
import org.jetlinks.community.notify.template.TemplateManager;
import org.jetlinks.core.Values;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.springframework.core.io.InputStreamResource;
import org.springframework.core.io.InputStreamSource;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.JavaMailSenderImpl;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.util.StringUtils;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Scheduler;
import reactor.core.scheduler.Schedulers;

import javax.annotation.Nonnull;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeUtility;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;

/**
 * 使用javax.mail进行邮件发送
 *
 * @author bsetfeng
 * @author zhouhao
 * @since 1.0
 **/
@Slf4j
public class DefaultEmailNotifier extends AbstractNotifier<EmailTemplate> {


    @Getter
    @Setter
    private JavaMailSender javaMailSender;

    @Getter
    @Setter
    private String sender;

    @Getter
    private final String notifierId;


    public static Scheduler scheduler = Schedulers.newElastic("email-notifier");

    public DefaultEmailNotifier(NotifierProperties properties, TemplateManager templateManager) {
        super(templateManager);
        notifierId = properties.getId();

        DefaultEmailProperties emailProperties = new JSONObject(properties.getConfiguration())
            .toJavaObject(DefaultEmailProperties.class);
        ValidatorUtils.tryValidate(emailProperties);

        JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
        mailSender.setHost(emailProperties.getHost());
        mailSender.setPort(emailProperties.getPort());
        mailSender.setUsername(emailProperties.getUsername());
        mailSender.setPassword(emailProperties.getPassword());
        mailSender.setJavaMailProperties(emailProperties.createJavaMailProperties());
        this.sender = emailProperties.getSender();
        this.javaMailSender = mailSender;
    }

    @Nonnull
    @Override
    public Mono<Void> send(@Nonnull EmailTemplate template, @Nonnull Values context) {
        return Mono.just(template)
            .map(temp -> convert(temp, context.getAllValues()))
            .flatMap(temp -> doSend(temp, template.getSendTo()));
    }

    @Nonnull
    @Override
    public Mono<Void> close() {
        return Mono.empty();
    }

    @Nonnull
    @Override
    public NotifyType getType() {
        return DefaultNotifyType.email;
    }

    @Nonnull
    @Override
    public Provider getProvider() {
        return EmailProvider.embedded;
    }

    protected Mono<Void> doSend(ParsedEmailTemplate template, List<String> sendTo) {
        return Mono
            .fromCallable(() -> {
                MimeMessage mimeMessage = this.javaMailSender.createMimeMessage();
                MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true, "utf-8");

                helper.setFrom(this.sender);
                helper.setTo(sendTo.toArray(new String[0]));
                helper.setSubject(template.getSubject());
                helper.setText(new String(template.getText().getBytes(), StandardCharsets.UTF_8), true);

                return Flux.fromIterable(template.getAttachments().entrySet())
                    .flatMap(entry -> Mono.zip(Mono.just(entry.getKey()), convertResource(entry.getValue())))
                    .doOnNext(tp -> Try.run(() -> helper.addAttachment(MimeUtility.encodeText(tp.getT1()), tp.getT2())).get())
                    .then(
                        Flux.fromIterable(template.getImages().entrySet())
                            .flatMap(entry -> Mono.zip(Mono.just(entry.getKey()), convertResource(entry.getValue())))
                            .doOnNext(tp -> Try.run(() -> helper.addInline(tp.getT1(), tp.getT2(), MediaType.APPLICATION_OCTET_STREAM_VALUE)).get())
                            .then()
                    ).thenReturn(mimeMessage)
                    ;

            })
            .publishOn(scheduler)
            .flatMap(Function.identity())
            .doOnNext(message -> this.javaMailSender.send(message))
            .then()
            ;
    }


    protected Mono<InputStreamSource> convertResource(String resource) {
        if (resource.startsWith("http")) {
            return WebClient.create()
                .get()
                .uri(resource)
                .accept(MediaType.APPLICATION_OCTET_STREAM)
                .exchange()
                .flatMap(rep -> rep.bodyToMono(Resource.class));
        } else {
            try {
                return Mono.just(new InputStreamResource(new FileInputStream(resource)));
            } catch (FileNotFoundException e) {
                return Mono.error(e);
            }
        }
    }


    protected ParsedEmailTemplate convert(EmailTemplate template, Map<String, Object> context) {
        String subject = template.getSubject();
        String text = template.getText();
        if (StringUtils.isEmpty(subject) || StringUtils.isEmpty(text)) {
            throw new BusinessException("模板内容错误，text 或者 subject 不能为空.");
        }
        String sendText = render(text, context);
        List<EmailTemplate.Attachment> tempAttachments = template.getAttachments();
        Map<String, String> attachments = new HashMap<>();
        if (tempAttachments != null) {
            for (EmailTemplate.Attachment tempAttachment : tempAttachments) {
                attachments.put(tempAttachment.getName(), render(tempAttachment.getLocation(), context));
            }
        }
        return ParsedEmailTemplate.builder()
            .attachments(attachments)
            .images(extractSendTextImage(sendText))
            .text(sendText)
            .subject(render(subject, context))
            .build();
    }


    private Map<String, String> extractSendTextImage(String sendText) {
        Map<String, String> images = new HashMap<>();
        Document doc = Jsoup.parse(sendText);
        for (Element src : doc.getElementsByTag("img")) {
            String s = src.attr("src");
            if (s.startsWith("http")) {
                continue;
            }
            String tempKey = IDGenerator.MD5.generate();
            src.attr("src", "cid:".concat(tempKey));
            images.put(tempKey, s);
        }
        return images;
    }

    private String render(String str, Map<String, Object> context) {
        return ExpressionUtils.analytical(str, context, "spel");
    }

}
