package com.ffii.fpsms.modules.common.mail.service

import com.ffii.core.exception.InternalServerErrorException
import com.ffii.core.utils.LocaleUtils
import com.ffii.fpsms.modules.common.ErrorCodes
import com.ffii.fpsms.modules.common.SettingNames
import com.ffii.fpsms.modules.common.mail.pojo.MailRequest
import com.ffii.fpsms.modules.common.mail.web.models.MailSaveRequest
import com.ffii.fpsms.modules.settings.entity.Settings
import com.ffii.fpsms.modules.settings.entity.SettingsRepository
import com.ffii.fpsms.modules.settings.service.SettingsService
import freemarker.cache.StringTemplateLoader
import freemarker.template.*
import jakarta.mail.MessagingException
import jakarta.mail.internet.InternetAddress
import org.apache.commons.logging.Log
import org.apache.commons.logging.LogFactory
import org.springframework.core.io.ByteArrayResource
import org.springframework.mail.javamail.MimeMessageHelper
import org.springframework.scheduling.annotation.Async
import org.springframework.security.crypto.password.PasswordEncoder
import org.springframework.stereotype.Service
import org.springframework.ui.freemarker.FreeMarkerTemplateUtils
import java.io.IOException
import java.text.ParseException
import java.util.*

@Service
open class MailService(
    private val mailSenderService: MailSenderService,
    private val freemarkerConfig: Configuration,
    private val settingsService: SettingsService,
    private val passwordEncoder: PasswordEncoder,
    private val settingsRepository: SettingsRepository,
    private val mailTemplateService: MailTemplateService,
) {
    protected val logger: Log = LogFactory.getLog(javaClass)

    @Throws(
        MessagingException::class,
        TemplateNotFoundException::class,
        MalformedTemplateNameException::class,
        ParseException::class,
        IOException::class,
        TemplateException::class
    )
    open fun doSend(mailRequests: List<MailRequest>, locale: Locale) {

        val sender = mailSenderService.get()

        for (mailRequest in mailRequests) {
            val mimeMessage = sender.createMimeMessage()

            val helper = MimeMessageHelper(mimeMessage, true, "UTF-8")
            mailRequest.subject?.let { helper.setSubject(it) }

            var template: Template? = null
            if (mailRequest.template != null) {
                template = try {
                    freemarkerConfig
                        .getTemplate((mailRequest.template + "_" + LocaleUtils.toLocaleStr(locale)) + ".ftl")
                } catch (e: TemplateNotFoundException) {
                    freemarkerConfig.getTemplate(mailRequest.template + ".ftl")
                }
            } else {
                // Create a FreeMarker configuration
                val configuration = Configuration(Configuration.VERSION_2_3_32)

                // Create a template loader and add the template content
                val templateLoader = StringTemplateLoader()
                templateLoader.putTemplate("myTemplate", mailRequest.templateContent)
                configuration.templateLoader = templateLoader

                try {
                    // Get the template by name
                    template = configuration.getTemplate("myTemplate")
                } catch (e: IOException) {
                    // Handle any IO exceptions
                }
            }

            helper.setText(
                FreeMarkerTemplateUtils.processTemplateIntoString(template!!, mailRequest.args),
                true
            )

            val from: InternetAddress?;
            if (mailRequest.from != null) {
                helper.setFrom(mailRequest.from!!)
                from = mailRequest.from
            } else {
                helper.setFrom(settingsService.getString(SettingNames.MAIL_SMTP_USERNAME))
                from = InternetAddress(settingsService.getString(SettingNames.MAIL_SMTP_USERNAME))
            }

            if (mailRequest.priority != null) helper.setPriority(mailRequest.priority!!)
            if (mailRequest.replyTo != null) helper.setReplyTo(mailRequest.replyTo!!)
            if (mailRequest.to.isNotEmpty()) helper.setTo(
                mailRequest.to.toTypedArray()
            )
            if (mailRequest.cc.isNotEmpty()) helper.setCc(
                mailRequest.cc.toTypedArray()
            )
            if (mailRequest.bcc.isNotEmpty()) helper.setBcc(
                mailRequest.bcc.toTypedArray()
            )

            if (mailRequest.attachments.isNotEmpty()) {
                for ((key, value) in mailRequest.attachments.entries) {
                    helper.addAttachment(key, ByteArrayResource(value))
                }
            }

            if (from != null) {
                sender.send(mimeMessage)
            }
        }
    }

    @Throws(ParseException::class)
    fun send(mailRequests: List<MailRequest>, locale: Locale) {
        try {
            doSend(mailRequests, locale)
        } catch (e: MessagingException) {
            throw InternalServerErrorException(ErrorCodes.SEND_EMAIL_ERROR, e)
        } catch (e: IOException) {
            throw InternalServerErrorException(ErrorCodes.SEND_EMAIL_ERROR, e)
        } catch (e: TemplateException) {
            throw InternalServerErrorException(ErrorCodes.SEND_EMAIL_ERROR, e)
        }
    }

    @Throws(ParseException::class)
    fun send(mailRequests: List<MailRequest>) {
        send(mailRequests, LocaleUtils.getLocale())
    }

    @Throws(ParseException::class)
    fun send(mailRequest: MailRequest, locale: Locale) {
        send(mutableListOf(mailRequest), locale)
    }

    @Async
    @Throws(ParseException::class)
    open fun asyncSend(mailRequests: List<MailRequest>, locale: Locale) {
        try {
            doSend(mailRequests, locale)
        } catch (e: MessagingException) {
            logger.error("send email error", e)
        } catch (e: IOException) {
            logger.error("send email error", e)
        } catch (e: TemplateException) {
            logger.error("send email error", e)
        }
    }

    @Async
    @Throws(ParseException::class)
    open fun asyncSend(mailRequest: MailRequest, locale: Locale) {
        asyncSend(mutableListOf(mailRequest), locale)
    }

    @Async
    @Throws(ParseException::class)
    open fun asyncSend(mailRequests: List<MailRequest>) {
        asyncSend(mailRequests, LocaleUtils.getLocale())
    }

    @Async
    @Throws(ParseException::class)
    open fun asyncSend(mailRequest: MailRequest) {
        asyncSend(mutableListOf(mailRequest))
    }

    open fun saveMail(request: MailSaveRequest): List<Settings> {
        // ------------------ save mail settings ------------------ //
        val settings = mutableListOf<Settings>()

        request.settings.forEach { setting ->
            val tempSetting = settingsRepository.findById(setting.id).orElseThrow()
            settings += tempSetting.apply {
                value = setting.value
            }
        }

        settingsRepository.saveAll(settings)

        // ------------------ save mail templates' details ------------------ //
        mailTemplateService.saveMailTemplates(request.templates)

        return settings
    }
}