package com.correx.apps.cli.commands

import com.correx.apps.cli.CorrexCli
import com.correx.apps.cli.DEFAULT_PORT
import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.parameters.arguments.argument
import com.github.ajalt.clikt.parameters.options.default
import com.github.ajalt.clikt.parameters.options.flag
import com.github.ajalt.clikt.parameters.options.option
import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.engine.cio.CIO
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.client.request.get
import io.ktor.serialization.kotlinx.json.json
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.Serializable
import kotlinx.serialization.builtins.ListSerializer
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonElement

@Serializable
data class EventRow(
    val sequence: Long,
    val eventId: String,
    val type: String,
    val timestamp: String,
    val causationId: String?,
    val correlationId: String?,
    val payload: JsonElement,
)

fun renderEventRows(rows: List<EventRow>): String {
    if (rows.isEmpty()) return "(no events)"
    val header = "%-6s  %-40s  %-30s  %s".format("seq", "type", "timestamp", "causation→")
    val lines = rows.map { r ->
        "%-6d  %-40s  %-30s  %s".format(
            r.sequence,
            r.type,
            r.timestamp,
            r.causationId ?: "-",
        )
    }
    return (listOf(header) + lines).joinToString("\n")
}

private fun String.dotEscape(): String = replace("\\", "\\\\").replace("\"", "\\\"")

fun renderDot(rows: List<EventRow>): String = buildString {
    appendLine("digraph session {")
    rows.forEach { r ->
        appendLine(""""${r.eventId.dotEscape()}" [label="${r.type.dotEscape()} #${r.sequence}"]""")
    }
    rows.forEach { r ->
        r.causationId?.let { causation ->
            appendLine(""""${r.eventId.dotEscape()}" -> "${causation.dotEscape()}"""")
        }
    }
    append("}")
}

class EventsCommand : CliktCommand(name = "events") {
    private val sessionId by argument("sessionId")
    private val type by option("--type")
    private val fromSeq by option("--from-seq")
    private val limit by option("--limit")
    private val host by option("--host").default("localhost")
    private val port by option("--port").default("$DEFAULT_PORT")
    private val dot by option("--dot").flag()

    private val eventsJson = Json { ignoreUnknownKeys = true }

    override fun run(): Unit = runBlocking {
        val portInt = port.toIntOrNull() ?: DEFAULT_PORT
        val outputJson = (currentContext.findRoot().command as? CorrexCli)?.json ?: false

        val params = buildList {
            type?.let { add("type=$it") }
            fromSeq?.let { add("fromSeq=$it") }
            limit?.let { add("limit=$it") }
        }.joinToString("&")
        val query = if (params.isNotEmpty()) "?$params" else ""
        val url = "http://$host:$portInt/sessions/$sessionId/events$query"

        val client = HttpClient(CIO) {
            install(ContentNegotiation) { json(eventsJson) }
        }

        runCatching {
            val rows = client.get(url).body<List<EventRow>>()
            when {
                dot -> println(renderDot(rows))
                outputJson -> println(eventsJson.encodeToString(ListSerializer(EventRow.serializer()), rows))
                else -> println(renderEventRows(rows))
            }
        }.getOrElse { e ->
            System.err.println("Error fetching events: ${e.message}")
        }

        client.close()
    }
}
