import com.correx.core.approvals.ApprovalProjector
import com.correx.core.approvals.DefaultApprovalReducer
import com.correx.core.approvals.DefaultApprovalRepository
import com.correx.core.approvals.domain.DefaultApprovalEngine
import com.correx.core.artifactstore.ArtifactStore
import com.correx.core.artifacts.DefaultArtifactReducer
import com.correx.core.events.events.ArtifactContentStoredEvent
import com.correx.core.events.events.EventMetadata
import com.correx.core.events.events.NewEvent
import com.correx.core.events.types.ArtifactId
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.inference.InferenceRepository
import com.correx.core.inference.InferenceState
import com.correx.core.journal.DecisionJournalProjector
import com.correx.core.journal.DefaultDecisionJournalReducer
import com.correx.core.journal.DefaultDecisionJournalRepository
import com.correx.core.kernel.orchestration.DefaultOrchestrationReducer
import com.correx.core.kernel.orchestration.DefaultSessionOrchestrator
import com.correx.core.kernel.orchestration.OrchestrationProjector
import com.correx.core.kernel.orchestration.OrchestrationRepository
import com.correx.core.kernel.orchestration.OrchestratorEngines
import com.correx.core.kernel.orchestration.OrchestratorRepositories
import com.correx.core.kernel.retry.DefaultRetryCoordinator
import com.correx.core.risk.DefaultRiskAssessor
import com.correx.core.sessions.DefaultSessionRepository
import com.correx.core.sessions.projections.replay.DefaultEventReplayer
import com.correx.core.sessions.projections.replay.EventReplayer
import com.correx.core.utils.TypeId
import com.correx.core.validation.pipeline.ValidationPipeline
import com.correx.infrastructure.persistence.InMemoryEventStore
import com.correx.infrastructure.persistence.artifact.LiveArtifactRepository
import com.correx.testing.fixtures.InferenceFixtures
import com.correx.testing.fixtures.context.ContextFixtures
import com.correx.testing.fixtures.transitions.TransitionFixtures
import com.correx.testing.kernel.MockSessionEventReplayer
import kotlinx.coroutines.runBlocking
import kotlinx.datetime.Clock
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
import java.util.UUID

class RehydrateTest {

    private val sessionId: SessionId = TypeId("session-rehydrate")

    // The CAS is content-addressed: the stored key is a content hash, NOT the
    // logical slot name. F-021 — rehydrate must resolve slot.name -> contentHash
    // via ArtifactContentStoredEvent and fetch by hash, never pass the slot name
    // (which is non-hex and would crash a real CAS) to ArtifactStore.get.
    private val slotName: ArtifactId = TypeId("analysis")
    private val contentHash: ArtifactId = TypeId("a1b2c3d4")
    private val artifactContent = """{"status":"success"}"""

    private val eventStore = InMemoryEventStore()

    private val fakeArtifactStore: ArtifactStore = object : ArtifactStore {
        override suspend fun put(bytes: ByteArray): ArtifactId = contentHash
        override suspend fun get(id: ArtifactId): ByteArray? =
            if (id == contentHash) artifactContent.toByteArray(Charsets.UTF_8) else null
        override suspend fun flushBefore(commit: suspend () -> Unit) = commit()
    }

    private val orchestrationRepository = OrchestrationRepository(
        DefaultEventReplayer(eventStore, OrchestrationProjector(DefaultOrchestrationReducer())),
    )

    private val inferenceRepository = InferenceRepository(
        object : EventReplayer<InferenceState> {
            override fun rebuild(sessionId: SessionId) = InferenceState()
        },
    )

    private val approvalRepository = DefaultApprovalRepository(
        DefaultEventReplayer(eventStore, ApprovalProjector(DefaultApprovalReducer())),
    )

    private val decisionJournalRepository = DefaultDecisionJournalRepository(
        DefaultEventReplayer(eventStore, DecisionJournalProjector(DefaultDecisionJournalReducer())),
    )

    private val repositories = OrchestratorRepositories(
        eventStore = eventStore,
        inferenceRepository = inferenceRepository,
        orchestrationRepository = orchestrationRepository,
        sessionRepository = DefaultSessionRepository(MockSessionEventReplayer()),
        artifactRepository = LiveArtifactRepository(eventStore, DefaultArtifactReducer()),
        approvalRepository = approvalRepository,
    )

    private val engines = OrchestratorEngines(
        transitionResolver = TransitionFixtures.simpleResolver(),
        contextPackBuilder = ContextFixtures.simpleBuilder(),
        inferenceRouter = InferenceFixtures.fixedRouter(),
        validationPipeline = ValidationPipeline(validators = emptyList()),
        approvalEngine = DefaultApprovalEngine(),
        riskAssessor = DefaultRiskAssessor(),
    )

    private val orchestrator = DefaultSessionOrchestrator(
        repositories = repositories,
        engines = engines,
        retryCoordinator = DefaultRetryCoordinator(eventStore),
        artifactStore = fakeArtifactStore,
        decisionJournalRepository = decisionJournalRepository,
    )

    private suspend fun appendContentStoredEvent() {
        eventStore.append(
            NewEvent(
                metadata = EventMetadata(
                    eventId = EventId(UUID.randomUUID().toString()),
                    sessionId = sessionId,
                    timestamp = Clock.System.now(),
                    schemaVersion = 1,
                    causationId = null,
                    correlationId = null,
                ),
                payload = ArtifactContentStoredEvent(
                    artifactId = slotName,
                    contentHash = contentHash,
                    sessionId = sessionId,
                    stageId = StageId("stage-1"),
                ),
            ),
        )
    }

    @Test
    fun `rehydrate resolves slot name to content hash and populates cache`(): Unit = runBlocking {
        appendContentStoredEvent()

        orchestrator.rehydrate(sessionId)

        // Cache is keyed by the logical slot name; content fetched from CAS by hash.
        val cached = orchestrator.validatedArtifactContent(sessionId, slotName)
        assertEquals(artifactContent, cached)
    }

    @Test
    fun `rehydrate is a no-op when there are no ArtifactContentStoredEvents`(): Unit = runBlocking {
        orchestrator.rehydrate(sessionId)

        val cached = orchestrator.validatedArtifactContent(sessionId, slotName)
        assertEquals(null, cached)
    }
}
