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.artifacts.DefaultArtifactReducer
import com.correx.core.events.events.CritiqueFinding
import com.correx.core.events.events.CritiqueFindingsRecordedEvent
import com.correx.core.events.events.CritiqueOutcome
import com.correx.core.events.events.CritiqueOutcomeCorrelatedEvent
import com.correx.core.events.events.CritiqueRole
import com.correx.core.events.events.CritiqueSeverity
import com.correx.core.events.events.CritiqueVerdict
import com.correx.core.events.events.EventMetadata
import com.correx.core.events.events.NewEvent
import com.correx.core.events.types.EventId
import com.correx.core.events.execution.RetryPolicy
import com.correx.core.events.types.SessionId
import com.correx.core.events.types.StageId
import com.correx.core.events.types.TransitionId
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.OrchestrationConfig
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.transitions.graph.StageConfig
import com.correx.core.transitions.graph.TransitionEdge
import com.correx.core.transitions.graph.WorkflowGraph
import com.correx.core.validation.pipeline.ValidationPipeline
import com.correx.infrastructure.persistence.InMemoryEventStore
import com.correx.infrastructure.persistence.artifact.LiveArtifactRepository
import com.correx.testing.contracts.fixtures.artifactstore.NoopArtifactStore
import com.correx.testing.fixtures.InferenceFixtures
import com.correx.testing.fixtures.context.ContextFixtures
import com.correx.testing.fixtures.cyclePolicyMissingValidator
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.Assertions.assertTrue
import org.junit.jupiter.api.Test

/**
 * End-to-end proof that the reviewer-loop runtime correlates recorded critique findings into
 * outcome events at loop resolution (BACKLOG §B-§6), and that those events feed the
 * `:core:critique` calibration projection. The LLM-side emission of findings is mocked here by
 * seeding [CritiqueFindingsRecordedEvent]s directly (that half is a separate model-gated step).
 */
class CritiqueCalibrationWiringTest {

    private val eventStore = InMemoryEventStore()
    private val sessionReplayer = MockSessionEventReplayer()
    private val sessionRepository = DefaultSessionRepository(sessionReplayer)
    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 retryCoordinator = DefaultRetryCoordinator(eventStore)

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

    private val orchestrator = DefaultSessionOrchestrator(
        repositories = repositories,
        engines = OrchestratorEngines(
            transitionResolver = TransitionFixtures.simpleResolver(),
            contextPackBuilder = ContextFixtures.simpleBuilder(),
            inferenceRouter = InferenceFixtures.fixedRouter(),
            validationPipeline = ValidationPipeline(validators = listOf(cyclePolicyMissingValidator())),
            approvalEngine = DefaultApprovalEngine(),
            riskAssessor = DefaultRiskAssessor(),
        ),
        retryCoordinator = retryCoordinator,
        artifactStore = NoopArtifactStore(),
        decisionJournalRepository = decisionJournalRepository,
    )

    private val defaultConfig = OrchestrationConfig(retryPolicy = RetryPolicy(maxAttempts = 1, backoffMs = 0))

    private fun finding(id: String) = CritiqueFinding(
        id = id,
        role = CritiqueRole.CODE_REVIEWER,
        severity = CritiqueSeverity.MAJOR,
        category = "correctness",
        message = "msg-$id",
    )

    private suspend fun seedReview(sessionId: SessionId, iteration: Int, verdict: CritiqueVerdict, findings: List<CritiqueFinding>) {
        eventStore.append(
            NewEvent(
                EventMetadata(EventId("rev-$iteration"), sessionId, Clock.System.now(), 1, null, null),
                CritiqueFindingsRecordedEvent(
                    sessionId = sessionId,
                    stageId = StageId("reviewer"),
                    role = CritiqueRole.CODE_REVIEWER,
                    modelHash = "model-A",
                    iteration = iteration,
                    verdict = verdict,
                    findings = findings,
                ),
            ),
        )
    }

    @Test
    fun `recorded findings are correlated at completion and feed the calibration projection`(): Unit = runBlocking {
        val sessionId = SessionId("calib-1")

        // Two review rounds: f1 fixed between them (→ UPHELD); f2 persists to an approved final (→ DISMISSED).
        seedReview(sessionId, 1, CritiqueVerdict.CHANGES_REQUESTED, listOf(finding("f1"), finding("f2")))
        seedReview(sessionId, 2, CritiqueVerdict.APPROVED, listOf(finding("f2")))

        val stageA = StageId("A")
        val done = StageId("done")
        val graph = WorkflowGraph(
            id = "calib-wiring",
            stages = mapOf(stageA to StageConfig(), done to StageConfig()),
            transitions = setOf(TransitionEdge(id = TransitionId("t1"), from = stageA, to = done, condition = { true })),
            start = stageA,
        )

        orchestrator.run(sessionId, graph, defaultConfig)

        // The orchestrator emits one outcome per recorded finding at completion — the events the
        // :core:critique calibration projection consumes (its folding is covered by that module's
        // DefaultCriticCalibrationReducerTest).
        val outcomes = eventStore.read(sessionId).mapNotNull { it.payload as? CritiqueOutcomeCorrelatedEvent }
        assertEquals(2, outcomes.size, "one outcome per recorded finding")
        assertEquals(CritiqueOutcome.UPHELD, outcomes.single { it.findingId == "f1" }.outcome)
        assertEquals(CritiqueOutcome.DISMISSED, outcomes.single { it.findingId == "f2" }.outcome)
        assertTrue(outcomes.all { it.modelHash == "model-A" && it.role == CritiqueRole.CODE_REVIEWER })
    }
}
