import com.correx.core.events.types.SessionId
import com.correx.core.events.types.StageId
import com.correx.core.transitions.conditions.Not
import com.correx.core.transitions.conditions.TasksReady
import com.correx.core.events.types.TransitionId
import com.correx.core.transitions.evaluation.EvaluationContext
import com.correx.core.transitions.evaluation.TransitionConditionEvaluator
import com.correx.core.transitions.graph.StageConfig
import com.correx.core.transitions.graph.TransitionCondition
import com.correx.core.transitions.graph.TransitionEdge
import com.correx.core.transitions.graph.WorkflowGraph
import com.correx.core.transitions.resolution.DefaultTransitionResolver
import com.correx.core.transitions.resolution.TransitionDecision
import com.correx.core.transitions.resolution.TransitionResolver
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertInstanceOf

class DefaultTransitionResolverTest {

    private val evaluator = TransitionConditionEvaluator { condition, context -> condition.evaluate(context) }

    private val resolver: TransitionResolver = DefaultTransitionResolver(evaluator)

    @Test
    fun `returns NoMatch when no outgoing transitions exist for current stage`() {
        val graph = graph(
            start = "stage-a",
            transitions = setOf(
                edge(
                    id = "t1",
                    from = "stage-a",
                    to = "stage-b",
                    condition = alwaysTrue(),
                ),
            ),
            stages = setOf("stage-a", "stage-b"),
        )

        val result = resolver.resolve(
            graph = graph,
            context = context(currentStage = "stage-b"),
        )

        assertEquals(
            TransitionDecision.NoMatch,
            result,
        )
    }

    @Test
    fun `returns Stay when all conditions evaluate to false`() {
        val graph = graph(
            start = "stage-a",
            transitions = setOf(
                edge(
                    id = "t1",
                    from = "stage-a",
                    to = "stage-b",
                    condition = alwaysFalse(),
                ),
            ),
        )

        val result = resolver.resolve(
            graph = graph,
            context = context(currentStage = "stage-a"),
        )

        assertEquals(
            TransitionDecision.Stay,
            result,
        )
    }

    @Test
    fun `returns matching transition when condition evaluates to true`() {
        val graph = graph(
            start = "stage-a",
            transitions = setOf(
                edge(
                    id = "t1",
                    from = "stage-a",
                    to = "stage-b",
                    condition = alwaysTrue(),
                ),
            ),
        )

        val result = resolver.resolve(
            graph = graph,
            context = context(currentStage = "stage-a"),
        )

        val move = assertInstanceOf<TransitionDecision.Move>(result)

        assertEquals(
            TransitionId("t1"),
            move.transitionId,
        )

        assertEquals(
            StageId("stage-b"),
            move.to,
        )
    }

    @Test
    fun `first matching transition wins deterministically`() {
        val graph = graph(
            start = "stage-a",
            transitions = setOf(
                edge(
                    id = "t2",
                    from = "stage-a",
                    to = "stage-c",
                    condition = alwaysTrue(),
                ),
                edge(
                    id = "t1",
                    from = "stage-a",
                    to = "stage-b",
                    condition = alwaysTrue(),
                ),
            ),
        )

        val result = resolver.resolve(
            graph = graph,
            context = context(currentStage = "stage-a"),
        )

        val move = assertInstanceOf<TransitionDecision.Move>(result)

        assertEquals(
            TransitionId("t1"),
            move.transitionId,
        )

        assertEquals(
            StageId("stage-b"),
            move.to,
        )
    }

    @Test
    fun `only transitions from current stage are evaluated`() {
        val graph = graph(
            start = "stage-a",
            transitions = setOf(
                edge(
                    id = "t1",
                    from = "stage-x",
                    to = "stage-y",
                    condition = alwaysTrue(),
                ),
                edge(
                    id = "t2",
                    from = "stage-a",
                    to = "stage-b",
                    condition = alwaysTrue(),
                ),
            ),
        )

        val result = resolver.resolve(
            graph = graph,
            context = context(currentStage = "stage-a"),
        )

        val move = assertInstanceOf<TransitionDecision.Move>(result)

        assertEquals(
            TransitionId("t2"),
            move.transitionId,
        )
    }

    private fun edge(
        id: String,
        from: String,
        to: String,
        condition: TransitionCondition,
    ): TransitionEdge {
        return TransitionEdge(
            id = TransitionId(id),
            from = StageId(from),
            to = StageId(to),
            condition = condition,
        )
    }

    private fun graph(
        start: String,
        transitions: Set<TransitionEdge>,
        stages: Set<String> = transitions.flatMap { listOf(it.from.value, it.to.value) }.toSet(),
    ) = WorkflowGraph(
        id = "workflow-test",
        start = StageId(start),
        stages = stages.associate { StageId(it) to StageConfig() },
        transitions = transitions,
    )

    private fun context(
        currentSession: String = "s1",
        currentStage: String,
    ): EvaluationContext {
        return EvaluationContext(
            sessionId = SessionId(currentSession),
            currentStage = StageId(currentStage),
            variables = emptyMap(),
        )
    }

    @Test
    fun `TasksReady loops while ready tasks remain and exits at zero`() {
        val graph = graph(
            start = "claim",
            transitions = setOf(
                edge(id = "loop", from = "claim", to = "claim", condition = TasksReady),
                edge(id = "exit", from = "claim", to = "done", condition = Not(TasksReady)),
            ),
            stages = setOf("claim", "done"),
        )
        val base = context(currentStage = "claim")

        assertEquals(
            StageId("claim"),
            (resolver.resolve(graph, base.copy(readyTaskCount = 2)) as TransitionDecision.Move).to,
        )
        assertEquals(
            StageId("done"),
            (resolver.resolve(graph, base.copy(readyTaskCount = 0)) as TransitionDecision.Move).to,
        )
    }

    private fun alwaysTrue() =
        TransitionCondition { true }

    @Suppress("UnusedPrivateMember")
    private fun alwaysFalse() =
        TransitionCondition { false }
}
