package com.cmeeting.email;

import cn.hutool.json.JSONUtil;
import com.azure.core.credential.AccessToken;
import com.azure.core.credential.TokenRequestContext;
import com.azure.identity.ClientSecretCredential;
import com.azure.identity.ClientSecretCredentialBuilder;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.cmeeting.ad.service.UserService;
import com.cmeeting.ad.vo.UserVo;
import com.cmeeting.exception.RobotBaseException;
import com.cmeeting.log.service.ProcessLogService;
import com.cmeeting.pojo.MeetEmailTemplate;
import com.cmeeting.service.MeetEmailTemplateService;
import com.cmeeting.util.AESUtils;
import com.cmeeting.util.RSAUtils;
import com.cmeeting.util.RedisUtils;
import com.cmeeting.vo.EmailPush;
import com.cmeeting.vo.StatisticsEmailPush;
import com.microsoft.graph.authentication.TokenCredentialAuthProvider;
import com.microsoft.graph.models.*;
import com.microsoft.graph.models.Message;
import com.microsoft.graph.requests.AttachmentCollectionPage;
import com.microsoft.graph.requests.AttachmentCollectionResponse;
import com.microsoft.graph.requests.GraphServiceClient;
import lombok.extern.slf4j.Slf4j;
import okhttp3.Request;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import javax.activation.DataHandler;
import javax.activation.DataSource;
import javax.activation.FileDataSource;
import javax.annotation.Resource;
import javax.mail.*;
import javax.mail.internet.*;
import java.io.File;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.text.MessageFormat;
import java.time.ZonedDateTime;
import java.util.*;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

@Slf4j
@Service
public class EmailSender {
    @Value("${email.sender}")
    private String SENDER;
    @Value("${email.push-switch}")
    private Boolean pushSwitch;
    @Value("${email.environment}")
    private String environment;
    @Value("${email.test-receiver}")
    private String testReceiver;
    @Value("${email.microsoft.clientId}")
    private String clientId;
    @Value("${email.microsoft.clientSecret}")
    private String clientSecret;
    @Value("${email.microsoft.tenantId}")
    private String tenantId;
    @Value("${aec.key}")
    private String aseKey;
    @Resource
    private ProcessLogService processLogService;
    @Resource
    private MeetEmailTemplateService meetEmailTemplateService;

    private static final Integer MAX_RETRY = 3;

    /**
     * 发送邮件，带附件
     *
     * @param emailPushBuilder
     * @return
     */
    public Boolean sendEmailWithAttachment(EmailPush emailPushBuilder) {
        log.info("sendEmailWithAttachment start, sender -> {}", SENDER);

        if (!pushSwitch) {
            log.info("【邮箱推送】：应用未开启邮件推送功能");
            throw new RuntimeException("【邮箱推送】：应用未开启邮件推送功能");
        }
        AtomicInteger retryCount = new AtomicInteger(0);
        Boolean isSent = false;
        String toEmail = "test".equals(environment) ? testReceiver : emailPushBuilder.getToEmail();
        String subject = emailPushBuilder.getSubject();
        String meetingRecordId = emailPushBuilder.getMeetingRecordId();
        String meetingId = emailPushBuilder.getMeetingId();
        String subMeetingId = emailPushBuilder.getSubMeetingId();
        Integer meetingInstanceId = emailPushBuilder.getMeetingInstanceId();
        String toUserCode = emailPushBuilder.getToUserCode();
        String toUser = emailPushBuilder.getToUser();
        Boolean emailPushAccess = emailPushBuilder.getEmailPushAccess();
        //用户自定义邮件推送许可
        if (emailPushAccess) {
            log.info("用户允许邮件推送，准备推送邮件至{}------", emailPushBuilder.getToEmail());
        } else {
            log.info("用户关闭了邮件推送，推送终止------");
            processLogService.log(meetingId,subMeetingId,"用户关闭了邮件推送，推送终止");
            throw new RuntimeException("用户关闭了邮件推送，推送终止");
        }

        if (StringUtils.isEmpty(toEmail)) {
            log.error("收件邮箱为空，推送失败");
            processLogService.log(meetingId,subMeetingId,"收件邮箱为空，推送失败。environment->"+environment+"，testReceiver->"+testReceiver+"，realEmail->"+emailPushBuilder.getToEmail());
            throw new RuntimeException("收件邮箱为空，推送失败");
        }
        log.info("准备开始邮件推送...");
        while (retryCount.intValue() < MAX_RETRY && isSent != null && !isSent) {
            try {
                ClientSecretCredential clientSecretCredential = new ClientSecretCredentialBuilder()
                        .clientId(RSAUtils.decrypt(clientId))
                        .clientSecret(RSAUtils.decrypt(clientSecret))
                        .tenantId(RSAUtils.decrypt(tenantId))
                        .build();
                try {
                    TokenRequestContext context = new TokenRequestContext()
                            .addScopes("https://graph.microsoft.com/.default");

                    AccessToken token = clientSecretCredential.getToken(context).block();
                    System.out.println(token != null ? token.getToken() : null);
                } catch (Exception e) {
                    log.error("获取访问令牌失败: {}", e.getMessage());
                }

                final TokenCredentialAuthProvider tokenCredAuthProvider = new TokenCredentialAuthProvider(Arrays.asList("https://graph.microsoft.com/.default"), clientSecretCredential);
                System.out.println("First Step Reached. ");

                GraphServiceClient<Request> graphClient = GraphServiceClient.builder().authenticationProvider(tokenCredAuthProvider).buildClient();

                com.microsoft.graph.models.Message message = new Message();
                message.subject = subject + "会议纪要";
                ItemBody body = new ItemBody();
                body.contentType = BodyType.HTML;

                if (StringUtils.isEmpty(toUserCode)) {
                    isSent = false;
                    processLogService.log(meetingId, subMeetingId, "【邮件推送异常】:收件人工号不能为空");
                    continue;
                }
                MeetEmailTemplate one = meetEmailTemplateService.getOne(new LambdaQueryWrapper<MeetEmailTemplate>()
                        .orderByDesc(MeetEmailTemplate::getCreateTime)
                        .select(MeetEmailTemplate::getContent).last("limit 1"));
                String emailContentTemplate = one.getContent();
                long expireTimestamp = ZonedDateTime.now().plusDays(1).toInstant().toEpochMilli();

                UserVo.Auth auth = new UserVo.Auth();
                auth.setId(toUserCode);
                auth.setNick(toUser);
                auth.setExpireDate(expireTimestamp);
                String s = JSONUtil.toJsonStr(auth);
                String encrypt = AESUtils.encrypt(s, aseKey);

                body.content = MessageFormat.format(emailContentTemplate, URLEncoder.encode(encrypt, StandardCharsets.UTF_8.name()), String.valueOf(meetingInstanceId));

                message.body = body;
                LinkedList<Recipient> toRecipientsList = new LinkedList<>();
                Recipient toRecipients = new Recipient();
                EmailAddress emailAddress = new EmailAddress();
                emailAddress.address = toEmail;
                toRecipients.emailAddress = emailAddress;
                toRecipientsList.add(toRecipients);
                message.toRecipients = toRecipientsList;
                //构建附件
                LinkedList<Attachment> attachmentsList = new LinkedList<>();
                for (EmailPush.Attachment attachment : emailPushBuilder.getAttachments()) {
                    FileAttachment attachments = new FileAttachment();
                    if (attachment.getName().endsWith(".pdf")) {
                        attachments.name = attachment.getName();
                        attachments.contentType = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
                    } else {
                        attachments.name = attachment.getName() + ".docx";
                        attachments.contentType = "application/pdf";
                    }
                    attachments.oDataType = "#microsoft.graph.fileAttachment";
                    attachments.contentBytes = attachment.getBytes();
                    attachmentsList.add(attachments);
                }

                AttachmentCollectionResponse attachmentCollectionResponse = new AttachmentCollectionResponse();
                attachmentCollectionResponse.value = attachmentsList;

                AttachmentCollectionPage attachmentCollectionPage = new AttachmentCollectionPage(attachmentCollectionResponse, null);
                message.attachments = attachmentCollectionPage;
                //以指定用户邮箱发送邮件
                graphClient.users(SENDER)
                        .sendMail(UserSendMailParameterSet.newBuilder().
                                withMessage(message).
                                withSaveToSentItems(true).build())
                        .buildRequest()
                        .post();

                log.info("邮件已成功发送: meetingRecordId->{}", meetingRecordId);
                isSent = true;
            } catch (Exception e) {
                retryCount.getAndIncrement();
                // 异常处理
                StringWriter sw = new StringWriter();
                PrintWriter pw = new PrintWriter(sw);
                e.printStackTrace(pw);
                processLogService.log(meetingId, subMeetingId, "【邮件推送异常】:" + sw.toString());
                if (retryCount.intValue() > MAX_RETRY) {
                    log.error("邮件发送达到最大重试次数: meetingId->{}", meetingId);
                    throw new RuntimeException(e);
                }
                // 指数退避
                try {
                    Thread.sleep((long) Math.pow(2, retryCount.intValue()) * 1000);
                } catch (InterruptedException ie) {
                    Thread.currentThread().interrupt();
                    throw new RuntimeException("邮件发送重试失败", ie);
                }
            }
        }
        return isSent;
    }

    public void emailStatisticsPush(StatisticsEmailPush emailPushBuilder) {
        log.info("emailStatisticsPush start, sender -> {}", SENDER);

        AtomicInteger retryCount = new AtomicInteger(0);
        boolean isSent = false;
        while (retryCount.intValue() < MAX_RETRY && !isSent) {
            try {
                ClientSecretCredential clientSecretCredential = new ClientSecretCredentialBuilder()
                        .clientId(RSAUtils.decrypt(clientId))
                        .clientSecret(RSAUtils.decrypt(clientSecret))
                        .tenantId(RSAUtils.decrypt(tenantId))
                        .build();
                try {
                    TokenRequestContext context = new TokenRequestContext().addScopes("https://graph.microsoft.com/.default");
                    AccessToken token = clientSecretCredential.getToken(context).block();
                    System.out.println(token != null ? token.getToken() : null);
                } catch (Exception e) {
                    log.error("统计邮件获取访问令牌失败: {}", e.getMessage());
                }
                final TokenCredentialAuthProvider tokenCredAuthProvider = new TokenCredentialAuthProvider(Collections.singletonList("https://graph.microsoft.com/.default"), clientSecretCredential);
                System.out.println("First Step Reached. ");
                GraphServiceClient<Request> graphClient = GraphServiceClient.builder().authenticationProvider(tokenCredAuthProvider).buildClient();

                com.microsoft.graph.models.Message message = new Message();
                message.subject = emailPushBuilder.getSubject();
                ItemBody body = new ItemBody();
                body.contentType = BodyType.HTML;
                body.content = emailPushBuilder.getContent();
                message.body = body;
                LinkedList<Recipient> toRecipientsList = new LinkedList<>();
                for (StatisticsEmailPush.ToEmail toEmail : emailPushBuilder.getToEmails()) {
                    Recipient toRecipients = new Recipient();
                    EmailAddress emailAddress = new EmailAddress();
                    emailAddress.address = toEmail.getEmail();
                    emailAddress.name = toEmail.getUser();
                    toRecipients.emailAddress = emailAddress;
                    toRecipientsList.add(toRecipients);
                }
                message.toRecipients = toRecipientsList;
                //构建附件
                LinkedList<Attachment> attachmentsList = new LinkedList<>();
                for (StatisticsEmailPush.Attachment attachment : emailPushBuilder.getAttachments()) {
                    FileAttachment attachments = new FileAttachment();
                    attachments.name = attachment.getName() + ".xlsx";
                    attachments.oDataType = "#microsoft.graph.fileAttachment";
                    attachments.contentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
                    attachments.contentBytes = attachment.getBytes();
                    attachmentsList.add(attachments);
                }

                AttachmentCollectionResponse attachmentCollectionResponse = new AttachmentCollectionResponse();
                attachmentCollectionResponse.value = attachmentsList;

                message.attachments = new AttachmentCollectionPage(attachmentCollectionResponse, null);
                //以指定用户邮箱发送邮件
                graphClient.users(SENDER)
                        .sendMail(UserSendMailParameterSet.newBuilder().
                                withMessage(message).
                                withSaveToSentItems(true).build())
                        .buildRequest()
                        .post();
                log.info("统计邮件已成功发送");
                isSent = true;
            } catch (Exception e) {
                retryCount.getAndIncrement();
                // 异常处理
                StringWriter sw = new StringWriter();
                PrintWriter pw = new PrintWriter(sw);
                e.printStackTrace(pw);
                if (retryCount.intValue() > MAX_RETRY) {
                    log.error("统计邮件发送达到最大重试次数");
                    throw new RuntimeException(e);
                }
                // 指数退避
                try {
                    Thread.sleep((long) Math.pow(2, retryCount.intValue()) * 1000);
                } catch (InterruptedException ie) {
                    Thread.currentThread().interrupt();
                    throw new RuntimeException("统计邮件发送重试失败", ie);
                }
            }
        }
    }
}