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.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 org.junit.jupiter.api.Test
import org.junit.jupiter.api.fail

class TransitionFuzzTest {

    private companion object {
        const val FUZZ_ITERATIONS = 500
        const val MAX_STEPS = 100
        val SEED: Long = System.getProperty("fuzz.seed")?.toLong() ?: System.currentTimeMillis()
    }

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

    @Test
    fun `graph-walk stays within declared edges and terminates`() {
        val rng = kotlin.random.Random(SEED)
        repeat(FUZZ_ITERATIONS) { iteration ->
            val iterSeed = rng.nextLong()
            val graph = randomGraph(kotlin.random.Random(iterSeed))
            val path1 = walk(graph, kotlin.random.Random(iterSeed))
            val path2 = walk(graph, kotlin.random.Random(iterSeed))
            val declaredEdgeIds = graph.transitions.map { it.id }.toSet()

            path1.forEach { transitionId ->
                if (transitionId !in declaredEdgeIds) {
                    fail(
                        "Invariant 1 violated: transition $transitionId not in declared graph. " +
                            "seed=$SEED iterSeed=$iterSeed iteration=$iteration"
                    )
                }
            }

            if (path1.size >= MAX_STEPS) {
                fail(
                    "Invariant 2 violated: walk did not terminate within $MAX_STEPS steps. " +
                        "seed=$SEED iterSeed=$iterSeed iteration=$iteration"
                )
            }

            if (path1 != path2) {
                fail(
                    "Invariant 3 violated: same seed produced different paths. " +
                        "path1=$path1 path2=$path2 seed=$SEED iterSeed=$iterSeed iteration=$iteration"
                )
            }
        }
    }

    private fun walk(graph: WorkflowGraph, rng: kotlin.random.Random): List<TransitionId> {
        val path = mutableListOf<TransitionId>()
        var currentStage = graph.start
        repeat(MAX_STEPS) {
            val context = randomContext(currentStage, rng)
            val graphWithConditions = randomizeConditions(graph, rng)
            val decision = resolver.resolve(graphWithConditions, context)
            if (decision is TransitionDecision.Move) {
                path.add(decision.transitionId)
                currentStage = decision.to
            } else {
                return path
            }
        }
        return path
    }

    private fun randomGraph(rng: kotlin.random.Random): WorkflowGraph {
        val stageCount = rng.nextInt(3, 9)
        val stageIds = (0 until stageCount).map { StageId("stage-$it") }
        val stages = stageIds.associateWith { StageConfig() }
        val start = stageIds.first()

        val edgeCount = rng.nextInt(stageCount, stageCount * 2 + 1)
        val edges = mutableSetOf<TransitionEdge>()
        repeat(edgeCount) { idx ->
            val from = stageIds[rng.nextInt(stageIds.size)]
            val to = stageIds[rng.nextInt(stageIds.size)]
            edges.add(
                TransitionEdge(
                    id = TransitionId("t-$idx"),
                    from = from,
                    to = to,
                    condition = TransitionCondition { true },
                )
            )
        }

        return WorkflowGraph(
            id = "fuzz-graph",
            start = start,
            stages = stages,
            transitions = edges,
        )
    }

    private fun randomizeConditions(graph: WorkflowGraph, rng: kotlin.random.Random): WorkflowGraph {
        val newEdges = graph.transitions.map { edge ->
            val outcome = rng.nextBoolean()
            edge.copy(condition = TransitionCondition { outcome })
        }.toSet()
        return graph.copy(transitions = newEdges)
    }

    private fun randomContext(currentStage: StageId, rng: kotlin.random.Random): EvaluationContext {
        val varCount = rng.nextInt(0, 4)
        val variables = (0 until varCount).associate { "var$it" to rng.nextInt(100).toString() }
        return EvaluationContext(
            sessionId = SessionId("fuzz-session"),
            currentStage = currentStage,
            variables = variables,
        )
    }
}
