import com.correx.core.context.builder.DefaultContextPackBuilder
import com.correx.core.context.compression.DefaultContextCompressor
import com.correx.core.context.model.ContextEntry
import com.correx.core.context.model.ContextLayer
import com.correx.core.context.model.EntryRole
import com.correx.core.context.model.TokenBudget
import com.correx.core.events.types.ContextEntryId
import com.correx.core.events.types.ContextPackId
import com.correx.core.events.types.SessionId
import com.correx.core.events.types.StageId
import kotlinx.coroutines.runBlocking
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test

// #289: a successful file_write/file_edit result echoes the whole file body back — it must be
// compacted to a receipt so it can't blow the context window, while reads and failures stay full.
class WriteReceiptCompactionTest {

    private val builder = DefaultContextPackBuilder(DefaultContextCompressor())
    private val bigBody = "x".repeat(20_000)

    private fun call(sourceId: String, name: String, path: String) = ContextEntry(
        id = ContextEntryId("call-$sourceId"),
        layer = ContextLayer.L2,
        content = """{"id":"$sourceId","function":{"name":"$name","arguments":"{\"path\":\"$path\"}"}}""",
        sourceType = "assistantToolCall",
        sourceId = sourceId,
        tokenEstimate = 40,
        role = EntryRole.ASSISTANT,
    )

    private fun result(sourceId: String, content: String) = ContextEntry(
        id = ContextEntryId("res-$sourceId"),
        layer = ContextLayer.L2,
        content = content,
        sourceType = "toolResult",
        sourceId = sourceId,
        tokenEstimate = content.length / 4,
        role = EntryRole.TOOL,
    )

    private suspend fun build(entries: List<ContextEntry>) =
        builder.build(ContextPackId("p"), SessionId("s"), StageId("st"), entries, TokenBudget(limit = 1_000_000))

    @Test
    fun `successful write result is compacted to a receipt`(): Unit = runBlocking {
        val entries = listOf(
            call("tc1", "file_write", "src/A.kt"),
            result("tc1", "[file_write exit=0]\n$bigBody"),
        )
        val res = build(entries).layers.values.flatten().first { it.sourceType == "toolResult" }
        assertFalse(res.content.contains(bigBody), "body should be elided from the receipt")
        assertTrue(res.content.contains("src/A.kt"), "receipt names the path")
        assertTrue(res.content.contains("body elided"), "receipt marks the elision")
    }

    @Test
    fun `read results and failed writes are left verbatim`(): Unit = runBlocking {
        val readBody = "[file_read exit=0]\n$bigBody"
        val failBody = "ERROR: could not write src/B.kt"
        val entries = listOf(
            call("r1", "file_read", "src/A.kt"),
            result("r1", readBody),
            call("w1", "file_write", "src/B.kt"),
            result("w1", failBody),
        )
        val results = build(entries).layers.values.flatten().filter { it.sourceType == "toolResult" }
        assertTrue(results.any { it.content == readBody }, "read result stays verbatim")
        assertTrue(results.any { it.content == failBody }, "failed write stays verbatim")
    }
}
