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

import com.ffii.fpsms.modules.common.mail.entity.MailTemplate
import com.ffii.fpsms.modules.common.mail.entity.MailTemplateRepository
import com.ffii.fpsms.modules.common.mail.web.models.DownloadMailTemplateResponse
import com.ffii.fpsms.modules.common.mail.web.models.MailTemplateRequest
import com.ffii.fpsms.modules.qc.service.QcResultService
import com.ffii.fpsms.modules.qc.web.model.QcResultInfoResponse
import com.ffii.fpsms.modules.stock.entity.StockInLineRepository
import com.ffii.fpsms.modules.stock.service.InventoryLotService
import com.itextpdf.html2pdf.ConverterProperties
import com.itextpdf.html2pdf.HtmlConverter
import com.itextpdf.layout.font.FontProvider
import org.jsoup.Jsoup
import org.springframework.stereotype.Service
import java.io.ByteArrayOutputStream
import java.math.BigDecimal
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import kotlin.jvm.optionals.getOrNull

@Service
open class MailTemplateService(
    private val mailTemplateRepository: MailTemplateRepository,
    private val stockInLineRepository: StockInLineRepository,
    private val inventoryLotService: InventoryLotService,
    private val qcResultService: QcResultService
) {
    data class MailTemplateHtml (
        val to: String = "",
        val subject: String = "",
        val content: String = "",
        val filename: String = "",
    )

    val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd")

    private fun formatFailedQcResultLine(result: QcResultInfoResponse): String {
        val label = result.name?.takeIf { it.isNotBlank() }
            ?: result.code?.takeIf { it.isNotBlank() }
            ?: "N/A"
        val criteria = result.description?.takeIf { it.isNotBlank() } ?: ""
        val base = if (criteria.isNotBlank()) "$label - $criteria" else label
        val measurementSuffix = result.measurementValue?.let { value ->
            val unit = result.unit.orEmpty()
            "；實測值：$value$unit"
        }.orEmpty()
        return base + measurementSuffix
    }

    fun allMailTemplates(): List<MailTemplate> {
        return mailTemplateRepository.findAllByDeletedIsFalse();
    }

    fun findById(id: Long): MailTemplate? {
        return mailTemplateRepository.findByIdAndDeletedIsFalse(id);
    }

    fun findByCode(code: String): MailTemplate? {
        return mailTemplateRepository.findByCodeAndDeletedIsFalse(code);
    }

    fun saveMailTemplate(request: MailTemplateRequest): MailTemplate {
        val mailTemplate = request.id?.let { findById(it) } ?: MailTemplate()

        mailTemplate.apply {
            to = request.to
            cc = request.cc
            code = request.code
            description = request.description
            type = request.type
            params = request.params
            subjectCht = request.subjectCht
            subjectEng = request.subjectEng
            contentCht = request.contentCht
            contentEng = request.contentEng
        }

        val response = mailTemplateRepository.saveAndFlush(mailTemplate)

        return response
    }

    fun saveMailTemplates(requests: List<MailTemplateRequest>) {
        val mailTemplates: List<MailTemplate> = mutableListOf()
        requests.forEach { request ->
            val mailTemplate = request.id?.let { findById(it) } ?: MailTemplate()

            mailTemplate.apply {
                to = request.to
                cc = request.cc
                code = request.code
                description = request.description
                type = request.type
                params = request.params
                subjectCht = request.subjectCht
                subjectEng = request.subjectEng
                contentCht = request.contentCht
                contentEng = request.contentEng
            }.let { mailTemplates.plus(it) }
        }

        mailTemplateRepository.saveAll(mailTemplates)
    }

    // ------------------------------------------- Stock In Line - Mail Template ------------------------------------------- //
    data class MailTemplateForStockInLineArgs(
        val supplierEmail: String = "",
        val supplierName: String = "",
        val dnNo: String = "",
        val receiptDate: String = "",
        val poNo: String = "",
        val supplierId: String = "",
        val itemNo: String = "",
        val itemName: String = "",
        val itemQty: String = "",
        val uom: String = "",
        val dnDate: String = "",
        val unitPrice: String = "",
        val receivedCompany: String = "",
        val lotNo: String = "",
        val qcResult: String = "",
        val receivedQty: String = "",
        val nonDelieveredQty: String = "",
        val rejectedQty: String = "",
        val qcDate: String = ""
    ) {
        fun applyToTemplate(template: String?): String {
            val content = template ?: "N/A"
            val args = mapOf(
                "\${supplierName}" to supplierName,
                "\${dnNo}" to dnNo,
                "\${receiptDate}" to receiptDate,
                "\${poNo}" to poNo,
                "\${supplierId}" to supplierId,
                "\${itemNo}" to itemNo,
                "\${itemName}" to itemName,
                "\${itemQty}" to itemQty,
                "\${uom}" to uom,
                "\${dnDate}" to dnDate,
                "\${unitPrice}" to unitPrice,
                "\${receivedCompany}" to receivedCompany,
                "\${lotNo}" to lotNo,
                "\${qcResult}" to qcResult,
                "\${receivedQty}" to receivedQty,
                "\${nonDelieveredQty}" to nonDelieveredQty,
                "\${rejectedQty}" to rejectedQty,
                "\${qcDate}" to qcDate
            )
            return args.entries.fold(content) { acc, (key, value) ->
                acc.replace(key, value)
            }
        }
    }

    fun getMailTemplateArgsForStockInLine(stockInLineId: Long): MailTemplateForStockInLineArgs {
        val stockInLine = stockInLineRepository.findById(stockInLineId).getOrNull() ?: throw NoSuchElementException("Cant find this stock in line.")

        // Value
        val zero = BigDecimal.ZERO
        val po = stockInLine.purchaseOrder
        val pol = stockInLine.purchaseOrderLine
        val item = stockInLine.item
        val supplierName = po?.supplier?.name ?: "N/A"
        val supplierEmail = (if((po?.supplier?.contactEmail).isNullOrEmpty()) "N/A" else po?.supplier?.contactEmail) ?: "N/A"
        val dnNo = stockInLine.dnNo ?: "N/A"
        val receiptDate = formatter.format(stockInLine.receiptDate) ?: "N/A"
        val poNo = po?.code ?: "N/A"
        val supplierId = po?.supplier?.code ?: "N/A" // Id?
        val itemNo = item?.code ?: "N/A"
        val itemName = item?.name ?: "N/A"
        val itemQty = (pol?.qty ?: zero).toString()
        val uom = pol?.uom?.udfudesc ?: "N/A"
//        val planDnDate = po?.estimatedArrivalDate?.let { formatter.format(it) } ?: "N/A"
        val dnDate = stockInLine.dnDate?.let { formatter.format(it) } ?: "N/A"
        val unitPrice = (pol?.up ?: zero).toString()
        val receivedCompany = po?.shop?.name ?: "N/A"
        val lotNo = stockInLine.productLotNo ?: "N/A"
        var qcDate = "N/A"
        val qcResult = qcResultService.getAllQcResultInfoByStockInLineId(stockInLineId).let { qcResults ->
            val filteredResult = qcResults
                .groupBy { Pair(it.stockInLineId, it.qcItemId) }
                .mapValues { (_, group) ->
                    group.maxByOrNull { it.recordDate ?: LocalDateTime.MIN }
                }
                .values
                .filterNotNull()
                .filter { !it.qcPassed }
            if (filteredResult.isNotEmpty()) {
                filteredResult.mapNotNull { it.recordDate }.maxOrNull()?.let { latest ->
                    qcDate = formatter.format(latest)
                }

                val tempDoc = Jsoup.parse("")
                val element = tempDoc.appendElement("ul")
                for (result in filteredResult) {
                    element.appendElement("li")
                        .text(formatFailedQcResultLine(result))
                }
                tempDoc.outerHtml()
            } else {
                "N/A"
            }
        } ?: "N/A"
        val rejectedQty = (stockInLine.acceptedQty ?: zero).minus(stockInLine.demandQty ?: zero).toString() // = reject qty
        val receivedQty = (stockInLine.acceptedQty ?: zero).toString() // = received qty
        val nonDelieveredQty = (zero).toString()

        // Final
        val args = MailTemplateForStockInLineArgs(
            supplierEmail = supplierEmail,
            supplierName = supplierName,
            dnNo = dnNo,
            receiptDate = receiptDate,
            poNo = poNo,
            supplierId = supplierId,
            itemNo = itemNo,
            itemName = itemName,
            itemQty = itemQty,
            uom = uom,
//            planDnDate = planDnDate,
            dnDate = dnDate,
            unitPrice = unitPrice,
            receivedCompany = receivedCompany,
            lotNo = lotNo,
            qcResult = qcResult,
            receivedQty = receivedQty,
            nonDelieveredQty = nonDelieveredQty,
            rejectedQty = rejectedQty,
            qcDate = qcDate
        );

        return args;
    }

    fun getMailTemplateHtmlForStockInLine(stockInLineId: Long): MailTemplateHtml {
        val emailTemplate = findByCode("RM-01") ?: throw NoSuchElementException("No RM-01 Email Template");
        val args = getMailTemplateArgsForStockInLine(stockInLineId);

        // HTML
        val to = args.supplierEmail
        val toHtmlStr = Jsoup.parse("").appendElement("p").text("To: $to").outerHtml()

        val subject = args.applyToTemplate(emailTemplate.subjectCht ?: "N/A")
//            .replace("\${supplierName}", supplierName)
//            .replace("\${poNo}", poNo)
//            .replace("\${itemNo}", itemNo)
        val subjectHtmlStr = Jsoup.parse("").appendElement("p").text("Subject: $subject").outerHtml()

        val content = args.applyToTemplate(emailTemplate.contentCht ?: "N/A")
//            .replace("\${supplierName}", supplierName)
//            .replace("\${dnNo}", dnNo)
//            .replace("\${dnDate}", dnDate)
//            .replace("\${poNo}", poNo)
//            .replace("\${supplierId}", supplierId)
//            .replace("\${itemNo}", itemNo)
//            .replace("\${itemName}", itemName)
//            .replace("\${itemQty}", itemQty)
//            .replace("\${uom}", uom)
//            .replace("\${planDnDate}", planDnDate)
//            .replace("\${unitPrice}", unitPrice)
//            .replace("\${receivedCompany}", receivedCompany)
//            .replace("\${lotNo}", lotNo)
//            .replace("\${qcResult}", qcResult)
//            .replace("\${acceptedQty}", receivedQty)
//            .replace("\${nonDelieveredQty}", nonDelieveredQty)
//            .replace("\${rejectedQty}", rejectedQty)
//            .replace("\${qcDate}", qcDate)
        val contentHtmlStr = Jsoup.parse("").appendElement("p").append("Content: $content").outerHtml();

        // Result
        val resultHtmlStr = toHtmlStr + subjectHtmlStr + contentHtmlStr;

        return MailTemplateHtml(
            to = toHtmlStr,
            subject = subjectHtmlStr,
            content = contentHtmlStr,
            filename = subject,
        );
    }

    fun getMailTemplatePdfForStockInLine(stockInLineId: Long): DownloadMailTemplateResponse {
        val htmlMailTemplate = getMailTemplateHtmlForStockInLine(stockInLineId)
        val resultHtmlStr = htmlMailTemplate.to + htmlMailTemplate.subject + htmlMailTemplate.content
//        println(resultHtmlStr)

        val resultPdf = ByteArrayOutputStream()
        val fp = FontProvider();
        fp.addStandardPdfFonts()
        fp.addFont("/fonts/msjh_0.ttf")
        val converterProperties = ConverterProperties()
        converterProperties.apply {
            fontProvider = fp
        }
        HtmlConverter.convertToPdf(resultHtmlStr, resultPdf, converterProperties)

        return DownloadMailTemplateResponse(
            file = resultPdf.toByteArray(),
            fileName = htmlMailTemplate.filename
        );
    }
}