package com.ffii.fpsms.modules.common.alert

import com.google.gson.Gson
import org.slf4j.LoggerFactory
import org.springframework.http.MediaType
import org.springframework.util.LinkedMultiValueMap
import org.springframework.web.reactive.function.BodyInserters
import org.springframework.web.reactive.function.client.WebClient
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.util.Locale

/**
 * Twilio Programmable Messaging (SMS or WhatsApp) — same API as:
 *
 * ```
 * curl 'https://api.twilio.com/2010-04-01/Accounts/{AccountSid}/Messages.json' -X POST \
 *   --data-urlencode 'To=whatsapp:+85292710394' \
 *   --data-urlencode 'From=whatsapp:+14155238886' \
 *   --data-urlencode 'ContentSid=HXb5b62575e6e4ff6129ad7c8efe1f983e' \
 *   --data-urlencode 'ContentVariables={"1":"12/1","2":"3pm"}' \
 *   -u {AccountSid}:{AuthToken}
 * ```
 */
class TwilioSmsSender(
    private val properties: SchedulerSyncAlertProperties,
    webClientBuilder: WebClient.Builder,
) : SmsSender {
    private val logger = LoggerFactory.getLogger(TwilioSmsSender::class.java)
    private val webClient = webClientBuilder.build()
    private val gson = Gson()

    override fun send(
        toNumbers: List<String>,
        body: String,
        job: String,
        code: String,
        detail: String,
    ) {
        sendRaw(toNumbers, body, buildContentVariables(job, code, detail, body))
    }

    /** Send with explicit template variables (for connectivity tests matching a Twilio sample). */
    fun sendWithTemplateVars(toNumbers: List<String>, var1: String, var2: String) {
        val json = gson.toJson(mapOf("1" to var1, "2" to var2))
        sendRaw(toNumbers, "FPSMS test", json)
    }

    private fun sendRaw(toNumbers: List<String>, body: String, contentVariablesJson: String?) {
        val sms = properties.sms
        require(sms.accountSid.isNotBlank()) { "scheduler.sync-alert.sms.accountSid is required for Twilio" }
        require(sms.authToken.isNotBlank()) { "scheduler.sync-alert.sms.authToken is required for Twilio" }
        require(sms.fromNumber.isNotBlank()) { "scheduler.sync-alert.sms.fromNumber is required for Twilio" }

        val url = "https://api.twilio.com/2010-04-01/Accounts/${sms.accountSid}/Messages.json"
        val from = formatAddress(sms.fromNumber, sms.channel)
        val useTemplate = sms.contentSid.isNotBlank()

        for (to in toNumbers) {
            val form = LinkedMultiValueMap<String, String>()
            form.add("To", formatAddress(to, sms.channel))
            form.add("From", from)
            if (useTemplate) {
                require(!contentVariablesJson.isNullOrBlank()) { "ContentVariables required when contentSid is set" }
                form.add("ContentSid", sms.contentSid)
                form.add("ContentVariables", contentVariablesJson)
                logger.info("Twilio WhatsApp ContentVariables={}", contentVariablesJson)
            } else {
                form.add("Body", body.take(1600))
            }

            try {
                webClient.post()
                    .uri(url)
                    .headers { it.setBasicAuth(sms.accountSid, sms.authToken) }
                    .contentType(MediaType.APPLICATION_FORM_URLENCODED)
                    .body(BodyInserters.fromFormData(form))
                    .retrieve()
                    .bodyToMono(String::class.java)
                    .block()
                logger.info("Twilio {} sent to {}", sms.channel, to)
            } catch (e: Exception) {
                logger.error("Twilio {} failed for {}: {}", sms.channel, to, e.message, e)
                throw e
            }
        }
    }

    private fun formatAddress(number: String, channel: String): String {
        val trimmed = number.trim()
        if (!channel.equals("whatsapp", ignoreCase = true)) {
            return trimmed
        }
        return if (trimmed.startsWith("whatsapp:", ignoreCase = true)) trimmed else "whatsapp:$trimmed"
    }

    private fun buildContentVariables(job: String, code: String, detail: String, body: String): String {
        val now = LocalDateTime.now()
        val summary = buildSummary(job, code, detail, body)
        val var1 = resolveContentVar(properties.sms.contentVar1, job, code, detail, body, summary, now)
        val var2 = resolveContentVar(properties.sms.contentVar2, job, code, detail, body, summary, now)
        return gson.toJson(mapOf("1" to var1, "2" to var2))
    }

    private fun buildSummary(job: String, code: String, detail: String, body: String): String {
        val j = job.ifBlank { "FPSMS" }
        return when {
            code.isNotBlank() && detail.isNotBlank() -> "$j $code — $detail"
            detail.isNotBlank() -> "$j — $detail"
            body.isNotBlank() -> body.removePrefix("FPSMS ").take(properties.sms.contentDetailMaxLength)
            else -> j
        }.take(properties.sms.contentDetailMaxLength)
    }

    private fun resolveContentVar(
        kind: String,
        job: String,
        code: String,
        detail: String,
        body: String,
        summary: String,
        now: LocalDateTime,
    ): String =
        when (kind.lowercase()) {
            "date" -> DATE_FORMAT.format(now)
            "time" -> TIME_FORMAT.format(now)
            "job" -> job.ifBlank { "FPSMS" }
            "code" -> code.ifBlank { "ALERT" }
            "detail" -> detail.take(properties.sms.contentDetailMaxLength).ifBlank { summary }
            "summary" -> summary
            else -> summary
        }

    companion object {
        private val DATE_FORMAT = DateTimeFormatter.ofPattern("d/M", Locale.ENGLISH)
        private val TIME_FORMAT = DateTimeFormatter.ofPattern("h:mma", Locale.ENGLISH)
    }
}
