package com.correx.core.kernel.orchestration

import com.correx.core.events.events.ContextAssembledEvent
import com.correx.core.events.events.ContextManifestEntry
import com.correx.core.events.events.EventMetadata
import com.correx.core.events.events.EventPayload
import com.correx.core.events.events.StoredEvent
import com.correx.core.events.types.EventId
import com.correx.core.events.types.SessionId
import com.correx.core.events.types.StageId
import com.correx.core.kernel.concept.conceptClassKey
import kotlinx.datetime.Clock
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertNotEquals
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
import java.util.UUID

/**
 * #306: the steer-away hint must fire once per retry occurrence, not on every context rebuild for
 * as long as the latest retry stays contradicted. [unconfirmedFixAlreadyDelivered] is the pure fold
 * that makes that "already delivered?" question replay-safe (folded over recorded
 * [ContextAssembledEvent] manifests, no new mutable state).
 */
class UnconfirmedFixDeliveryTest {

    private val sessionId = SessionId("s1")

    private fun stored(sessionSequence: Long, payload: EventPayload) = StoredEvent(
        metadata = EventMetadata(
            eventId = EventId(UUID.randomUUID().toString()),
            sessionId = sessionId,
            timestamp = Clock.System.now(),
            schemaVersion = 1,
            causationId = null,
            correlationId = null,
        ),
        sequence = sessionSequence,
        sessionSequence = sessionSequence,
        payload = payload,
    )

    private fun assembled(sessionSequence: Long, sourceType: String, sourceId: String) = stored(
        sessionSequence,
        ContextAssembledEvent(
            sessionId = sessionId,
            stageId = StageId("scaffold_frontend"),
            contextPackId = "pack-$sessionSequence",
            entries = listOf(
                ContextManifestEntry(sourceType, sourceId, tokenEstimate = 10, layer = "L1", role = "USER"),
            ),
            timestampMs = 0L,
        ),
    )

    @Test
    fun `not yet delivered for a fresh retry`() {
        val events = listOf(assembled(1, "unconfirmedFix", "other-class"))
        assertFalse(unconfirmedFixAlreadyDelivered(events, afterSequence = 5, classKey = "stage:x"))
    }

    @Test
    fun `delivered once is not delivered again on the next rebuild for the same retry`() {
        // retry lands at seq 5; the hint is delivered in the ContextAssembledEvent at seq 6
        // (first context build after the retry). A LATER rebuild for that same retry (still the
        // latest one, unchanged) must see it already delivered.
        val events = listOf(assembled(6, "unconfirmedFix", "stage:x"))
        assertTrue(unconfirmedFixAlreadyDelivered(events, afterSequence = 5, classKey = "stage:x"))
    }

    @Test
    fun `a fresh recurrence of the same class after a new retry earns one more delivery`() {
        // Prior delivery at seq 6 for the FIRST retry (afterSequence=5). A NEW retry of the same
        // class lands later (seq 20) — the delivery check for the new retry only looks after seq 20,
        // so the stale seq-6 delivery no longer counts.
        val events = listOf(assembled(6, "unconfirmedFix", "stage:x"))
        assertFalse(unconfirmedFixAlreadyDelivered(events, afterSequence = 20, classKey = "stage:x"))
    }

    @Test
    fun `a routing dead-end and a build failure never collapse into one classKey`() {
        val routing = conceptClassKey("stage", "no transition condition matched from stage scaffold_frontend")
        val build = conceptClassKey("build", "no transition condition matched from stage scaffold_frontend")
        assertNotEquals(routing, build)
    }
}
