package com.correx.core.kernel.orchestration

import com.correx.core.artifacts.kind.KindContractTable
import com.correx.core.events.events.BuildPrerequisiteBootstrapAttemptedEvent
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.transitions.graph.StageConfig
import kotlinx.datetime.Instant
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertNull
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test

class BuildPrerequisiteDecisionTest {

    @Test
    fun `build gate toolchain follows the stage produced kind`() {
        assertEquals(KindContractTable.Toolchain.NODE, toolchainForPaths(listOf("frontend/package.json")))
        assertEquals(KindContractTable.Toolchain.JVM, toolchainForPaths(listOf("core/kernel/FooService.kt")))
    }

    private val reason =
        "stage impl repeatedly referenced missing build prerequisite 'frontend/package.json' " +
            "(3 blocked attempts). Create or repair the project setup before continuing."

    @Test
    fun `decision maps the three facts to one bounded path`() {
        // not writable → route to a stage that can create the file.
        assertEquals(
            PrerequisiteAction.ROUTE_TO_RECOVERY,
            decidePrerequisiteAction(writable = false, inScope = true, bootstrapBudgetLeft = true),
        )
        // writable but out of scope → ask, don't guess.
        assertEquals(
            PrerequisiteAction.CLARIFY,
            decidePrerequisiteAction(writable = true, inScope = false, bootstrapBudgetLeft = true),
        )
        // writable, in scope, budget → grant the one bootstrap turn.
        assertEquals(
            PrerequisiteAction.BOOTSTRAP,
            decidePrerequisiteAction(writable = true, inScope = true, bootstrapBudgetLeft = true),
        )
        // writable, in scope, budget spent → escalate (the one turn didn't clear it).
        assertEquals(
            PrerequisiteAction.ESCALATE,
            decidePrerequisiteAction(writable = true, inScope = true, bootstrapBudgetLeft = false),
        )
    }

    @Test
    fun `path is extracted from the block reason`() {
        assertEquals("frontend/package.json", buildPrerequisitePath(reason))
        assertNull(buildPrerequisitePath("some unrelated failure"))
    }

    @Test
    fun `an unconstrained stage owns everything but a scoped stage only its territory`() {
        val unconstrained = StageConfig()
        assertTrue(pathInDeclaredScope(unconstrained, "frontend/package.json"))

        val scoped = StageConfig(touches = listOf("frontend/**"))
        assertTrue(pathInDeclaredScope(scoped, "frontend/package.json"))
        assertFalse(pathInDeclaredScope(scoped, "backend/package.json"))

        val byExpected = StageConfig(touches = listOf("src/**"), expectedFiles = listOf("package.json"))
        assertTrue(pathInDeclaredScope(byExpected, "package.json"))
    }

    @Test
    fun `bootstrap budget counts prior grants for this stage only`() {
        val stage = StageId("impl")
        assertEquals(0, bootstrapAttemptCount(emptyList(), stage))
        val events = listOf(
            bootstrap(stage, "package.json"),
            bootstrap(StageId("other"), "package.json"),
        )
        assertEquals(1, bootstrapAttemptCount(events, stage))
    }

    private var seq = 0L
    private fun bootstrap(stageId: StageId, path: String) = StoredEvent(
        metadata = EventMetadata(
            eventId = EventId("e${seq++}"),
            sessionId = SessionId("s1"),
            timestamp = Instant.parse("2026-01-01T00:00:00Z"),
            schemaVersion = 1,
            causationId = null,
            correlationId = null,
        ),
        sequence = seq,
        sessionSequence = seq,
        payload = BuildPrerequisiteBootstrapAttemptedEvent(SessionId("s1"), stageId, path, "why") as EventPayload,
    )
}
