package com.correx.core.transitions.conditions

import com.correx.core.artifacts.ArtifactState
import com.correx.core.events.types.ArtifactId
import com.correx.core.events.types.ArtifactLifecyclePhase
import com.correx.core.events.types.SessionId
import com.correx.core.events.types.StageId
import com.correx.core.transitions.evaluation.EvaluationContext
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertThrows
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test

class TransitionConditionTest {

    private val baseContext = EvaluationContext(
        sessionId = SessionId("test-session"),
        currentStage = StageId("stage-1"),
    )

    private fun contextWith(
        artifacts: Map<ArtifactId, ArtifactState> = emptyMap(),
        variables: Map<String, String> = emptyMap(),
    ) = baseContext.copy(artifacts = artifacts, variables = variables)

    // AlwaysTrue

    @Test
    fun `AlwaysTrue returns true`() {
        assertTrue(AlwaysTrue.evaluate(baseContext))
    }

    // TasksReady

    @Test
    fun `TasksReady true when ready count positive`() {
        assertTrue(TasksReady.evaluate(baseContext.copy(readyTaskCount = 1)))
    }

    @Test
    fun `TasksReady false when no ready tasks`() {
        assertFalse(TasksReady.evaluate(baseContext))
    }

    // ArtifactPresent

    @Test
    fun `ArtifactPresent returns true when artifact in map`() {
        val id = ArtifactId("a1")
        val ctx = contextWith(artifacts = mapOf(id to ArtifactState()))
        assertTrue(ArtifactPresent(id).evaluate(ctx))
    }

    @Test
    fun `ArtifactPresent returns false when artifact absent`() {
        val ctx = contextWith()
        assertFalse(ArtifactPresent(ArtifactId("a1")).evaluate(ctx))
    }

    // ArtifactAbsent

    @Test
    fun `ArtifactAbsent returns false when artifact in map`() {
        val id = ArtifactId("a1")
        val ctx = contextWith(artifacts = mapOf(id to ArtifactState()))
        assertFalse(ArtifactAbsent(id).evaluate(ctx))
    }

    @Test
    fun `ArtifactAbsent returns true when artifact absent`() {
        val ctx = contextWith()
        assertTrue(ArtifactAbsent(ArtifactId("a1")).evaluate(ctx))
    }

    // ArtifactValidated

    @Test
    fun `ArtifactValidated returns true when phase is VALIDATED`() {
        val id = ArtifactId("a1")
        val ctx = contextWith(artifacts = mapOf(id to ArtifactState(phase = ArtifactLifecyclePhase.VALIDATED)))
        assertTrue(ArtifactValidated(id).evaluate(ctx))
    }

    @Test
    fun `ArtifactValidated returns false when phase is CREATED`() {
        val id = ArtifactId("a1")
        val ctx = contextWith(artifacts = mapOf(id to ArtifactState(phase = ArtifactLifecyclePhase.CREATED)))
        assertFalse(ArtifactValidated(id).evaluate(ctx))
    }

    @Test
    fun `ArtifactValidated returns false when phase is REJECTED`() {
        val id = ArtifactId("a1")
        val ctx = contextWith(artifacts = mapOf(id to ArtifactState(phase = ArtifactLifecyclePhase.REJECTED)))
        assertFalse(ArtifactValidated(id).evaluate(ctx))
    }

    @Test
    fun `ArtifactValidated returns false when artifact absent`() {
        val ctx = contextWith()
        assertFalse(ArtifactValidated(ArtifactId("a1")).evaluate(ctx))
    }

    // VariableEquals

    @Test
    fun `VariableEquals returns true when key matches value`() {
        val ctx = contextWith(variables = mapOf("status" to "done"))
        assertTrue(VariableEquals("status", "done").evaluate(ctx))
    }

    @Test
    fun `VariableEquals returns false when key missing`() {
        val ctx = contextWith()
        assertFalse(VariableEquals("status", "done").evaluate(ctx))
    }

    @Test
    fun `VariableEquals returns false when value differs`() {
        val ctx = contextWith(variables = mapOf("status" to "pending"))
        assertFalse(VariableEquals("status", "done").evaluate(ctx))
    }

    // AllOf

    @Test
    fun `AllOf with empty list returns true (vacuous truth)`() {
        assertTrue(AllOf(emptyList()).evaluate(baseContext))
    }

    @Test
    fun `AllOf with all true conditions returns true`() {
        assertTrue(AllOf(listOf(AlwaysTrue, AlwaysTrue)).evaluate(baseContext))
    }

    @Test
    fun `AllOf with one false condition returns false`() {
        val alwaysFalse = Not(AlwaysTrue)
        assertFalse(AllOf(listOf(AlwaysTrue, alwaysFalse)).evaluate(baseContext))
    }

    // AnyOf

    @Test
    fun `AnyOf with empty list returns false (vacuous falsity)`() {
        assertFalse(AnyOf(emptyList()).evaluate(baseContext))
    }

    @Test
    fun `AnyOf with one true condition returns true`() {
        val alwaysFalse = Not(AlwaysTrue)
        assertTrue(AnyOf(listOf(alwaysFalse, AlwaysTrue)).evaluate(baseContext))
    }

    @Test
    fun `AnyOf with all false conditions returns false`() {
        val alwaysFalse = Not(AlwaysTrue)
        assertFalse(AnyOf(listOf(alwaysFalse, alwaysFalse)).evaluate(baseContext))
    }

    // Not

    @Test
    fun `Not inverts true to false`() {
        assertFalse(Not(AlwaysTrue).evaluate(baseContext))
    }

    @Test
    fun `Not inverts false to true`() {
        assertTrue(Not(Not(AlwaysTrue)).evaluate(baseContext))
    }

    // ArtifactFieldEquals — flat field access

    @Test
    fun `ArtifactFieldEquals matches flat field with eq`() {
        val id = ArtifactId("execution_result")
        val json = """{"status":"failed","command":"ls","exitCode":1,"stdout":"","stderr":"error"}"""
        val ctx = baseContext.copy(artifactContent = mapOf(id to json))
        assertTrue(ArtifactFieldEquals(id, "status", "failed").evaluate(ctx))
    }

    @Test
    fun `ArtifactFieldEquals does not match different value`() {
        val id = ArtifactId("execution_result")
        val json = """{"status":"success","exitCode":0}"""
        val ctx = baseContext.copy(artifactContent = mapOf(id to json))
        assertFalse(ArtifactFieldEquals(id, "status", "failed").evaluate(ctx))
    }

    @Test
    fun `ArtifactFieldEquals with neq matches different value`() {
        val id = ArtifactId("execution_result")
        val json = """{"status":"success","exitCode":0}"""
        val ctx = baseContext.copy(artifactContent = mapOf(id to json))
        assertTrue(ArtifactFieldEquals(id, "status", "failed", operator = FieldOperator.NEQ).evaluate(ctx))
    }

    // ArtifactFieldEquals — nested field access (dot notation)

    @Test
    fun `ArtifactFieldEquals resolves nested fields via dot notation`() {
        val id = ArtifactId("execution_result")
        val json = """{"execution":{"result":{"exitCode":1}}}"""
        val ctx = baseContext.copy(artifactContent = mapOf(id to json))
        assertTrue(ArtifactFieldEquals(id, "execution.result.exitCode", "1").evaluate(ctx))
    }

    // ArtifactFieldEquals — numeric operators

    @Test
    fun `ArtifactFieldEquals gt matches larger number`() {
        val id = ArtifactId("execution_result")
        val json = """{"exitCode":5}"""
        val ctx = baseContext.copy(artifactContent = mapOf(id to json))
        assertTrue(ArtifactFieldEquals(id, "exitCode", "0", operator = FieldOperator.GT).evaluate(ctx))
    }

    @Test
    fun `ArtifactFieldEquals gt does not match smaller number`() {
        val id = ArtifactId("execution_result")
        val json = """{"exitCode":0}"""
        val ctx = baseContext.copy(artifactContent = mapOf(id to json))
        assertFalse(ArtifactFieldEquals(id, "exitCode", "1", operator = FieldOperator.GT).evaluate(ctx))
    }

    @Test
    fun `ArtifactFieldEquals gte matches equal number`() {
        val id = ArtifactId("execution_result")
        val json = """{"exitCode":1}"""
        val ctx = baseContext.copy(artifactContent = mapOf(id to json))
        assertTrue(ArtifactFieldEquals(id, "exitCode", "1", operator = FieldOperator.GTE).evaluate(ctx))
    }

    @Test
    fun `ArtifactFieldEquals lt matches smaller number`() {
        val id = ArtifactId("execution_result")
        val json = """{"exitCode":0}"""
        val ctx = baseContext.copy(artifactContent = mapOf(id to json))
        assertTrue(ArtifactFieldEquals(id, "exitCode", "1", operator = FieldOperator.LT).evaluate(ctx))
    }

    @Test
    fun `ArtifactFieldEquals lte matches equal number`() {
        val id = ArtifactId("execution_result")
        val json = """{"exitCode":1}"""
        val ctx = baseContext.copy(artifactContent = mapOf(id to json))
        assertTrue(ArtifactFieldEquals(id, "exitCode", "1", operator = FieldOperator.LTE).evaluate(ctx))
    }

    // ArtifactFieldEquals — error cases

    @Test
    fun `ArtifactFieldEquals throws on missing artifact`() {
        val ctx = baseContext.copy(artifactContent = emptyMap())
        assertThrows(IllegalStateException::class.java) {
            ArtifactFieldEquals(ArtifactId("nonexistent"), "status", "failed").evaluate(ctx)
        }
        // Also catches the error() call
    }

    @Test
    fun `ArtifactFieldEquals throws on missing field`() {
        val id = ArtifactId("execution_result")
        val json = """{"status":"success"}"""
        val ctx = baseContext.copy(artifactContent = mapOf(id to json))
        val ex = assertThrows(IllegalStateException::class.java) {
            ArtifactFieldEquals(id, "missingField", "value").evaluate(ctx)
        }
        assertTrue(ex.message?.contains("not found") == true)
    }

    @Test
    fun `ArtifactFieldEquals throws numeric operator on non-numeric field`() {
        val id = ArtifactId("execution_result")
        val json = """{"status":"success"}"""
        val ctx = baseContext.copy(artifactContent = mapOf(id to json))
        val ex = assertThrows(IllegalStateException::class.java) {
            ArtifactFieldEquals(id, "status", "0", operator = FieldOperator.GT).evaluate(ctx)
        }
        assertTrue(ex.message?.contains("non-numeric") == true)
    }

    @Test
    fun `ArtifactFieldEquals neq with string works correctly`() {
        val id = ArtifactId("execution_result")
        val json = """{"status":"success"}"""
        val ctx = baseContext.copy(artifactContent = mapOf(id to json))
        assertTrue(ArtifactFieldEquals(id, "status", "failed", operator = FieldOperator.NEQ).evaluate(ctx))
        assertFalse(ArtifactFieldEquals(id, "status", "success", operator = FieldOperator.NEQ).evaluate(ctx))
    }
}
