/*
 * SPDX-License-Identifier: GPL-3.0-or-later
 */
package com.runicgateway.app.core.push

import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonNull
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.jsonPrimitive

/**
 * The content-free push tickle the backend publishes (PLAN.md §11): `{ stream, ref }`
 * and nothing sensitive. [ref] is an opaque hint (a serial / city / timestamp) the
 * app *could* use to pull real content over the authenticated API; v1 just deep-links
 * to the stream's screen, so it is carried but not otherwise interpreted.
 */
@Serializable
data class PushTickle(
    val stream: String,
    val ref: String? = null,
)

/**
 * Parse a tickle out of an ntfy SSE `data:` frame. ntfy wraps our published body in
 * its own envelope — `{ event, topic, message, … }` — where `message` is the exact
 * string we POSTed (our `{ stream, ref }` JSON). Only `event == "message"` frames
 * carry a payload; `open` / `keepalive` frames return null, as does any malformed or
 * unrecognized body (dropped, never thrown — §7). Pure + `internal` for unit testing.
 */
internal fun parseNtfyTickle(json: Json, data: String): PushTickle? {
    val trimmed = data.trim()
    if (trimmed.isEmpty() || trimmed.startsWith(":")) return null
    return try {
        val envelope = json.parseToJsonElement(trimmed) as? JsonObject ?: return null
        val event = envelope["event"]?.jsonPrimitive?.content
        // ntfy lifecycle frames ("open", "keepalive", "poll_request") carry no message.
        if (event != null && event != "message") return null
        val messageEl = envelope["message"] ?: return null
        if (messageEl is JsonNull) return null
        val message = messageEl.jsonPrimitive.content
        decodeTickle(json, message)
    } catch (_: Exception) {
        null
    }
}

/** Decode our own `{ stream, ref }` body; a blank/missing stream is not a tickle. */
internal fun decodeTickle(json: Json, body: String): PushTickle? = try {
    val tickle = json.decodeFromString(PushTickle.serializer(), body.trim())
    tickle.takeIf { it.stream.isNotBlank() }
} catch (_: Exception) {
    null
}
