package com.correx.apps.server.inference

import com.correx.core.events.events.CapabilityGapVerdict
import com.correx.core.events.types.ProviderId
import com.correx.core.events.types.SessionId
import com.correx.core.events.types.StageId
import com.correx.core.inference.CapabilityScore
import com.correx.core.inference.FinishReason
import com.correx.core.inference.InferenceProvider
import com.correx.core.inference.InferenceRequest
import com.correx.core.inference.InferenceResponse
import com.correx.core.inference.InferenceRouter
import com.correx.core.inference.ModelCapability
import com.correx.core.inference.ProviderHealth
import com.correx.core.inference.Token
import com.correx.core.inference.Tokenizer
import com.correx.core.inference.TokenUsage
import com.correx.core.kernel.orchestration.CapabilityGapInput
import com.correx.core.tools.contract.ToolCapability
import kotlinx.coroutines.runBlocking
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test

private class StubTokenizer : Tokenizer {
    override suspend fun tokenize(text: String): List<Token> = emptyList()
    override suspend fun countTokens(text: String): Int = text.length / 4
}

private class StubProvider(private val response: String) : InferenceProvider {
    override val id = ProviderId("stub")
    override val name = "stub"
    override val tokenizer = StubTokenizer()
    override suspend fun infer(request: InferenceRequest): InferenceResponse = InferenceResponse(
        requestId = request.requestId,
        text = response,
        finishReason = FinishReason.Stop,
        tokensUsed = TokenUsage(0, 0),
        latencyMs = 0,
    )
    override suspend fun healthCheck(): ProviderHealth = ProviderHealth.Healthy
    override fun capabilities(): Set<CapabilityScore> = emptySet()
}

private class StubRouter(private val provider: InferenceProvider) : InferenceRouter {
    override suspend fun route(
        stageId: StageId,
        requiredCapabilities: Set<ModelCapability>,
    ): InferenceProvider = provider
}

class CapabilityGapReflectorImplTest {

    private val gap = CapabilityGapInput(
        stageId = "apply",
        impliedCapability = ToolCapability.FILE_WRITE,
        phrase = "create",
        grantedCapabilities = emptySet(),
    )

    @Test
    fun `a RESOLVED verdict from the model is parsed with its detail`(): Unit = runBlocking {
        val response =
            """{"reflections":[{"stage":"apply","verdict":"RESOLVED","detail":"file_write already covers this"}]}"""
        val reflector = CapabilityGapReflectorImpl(StubRouter(StubProvider(response)))
        val result = reflector.reflect(SessionId("s1"), listOf(gap))
        assertEquals(1, result.size)
        assertEquals(CapabilityGapVerdict.RESOLVED, result.single().verdict)
        assertEquals("file_write already covers this", result.single().detail)
    }

    @Test
    fun `a NEEDS_TOOL verdict from the model is parsed with its justification`(): Unit = runBlocking {
        val response =
            """{"reflections":[{"stage":"apply","verdict":"NEEDS_TOOL","detail":"no existing tool can do this"}]}"""
        val reflector = CapabilityGapReflectorImpl(StubRouter(StubProvider(response)))
        val result = reflector.reflect(SessionId("s1"), listOf(gap))
        assertEquals(CapabilityGapVerdict.NEEDS_TOOL, result.single().verdict)
        assertEquals("no existing tool can do this", result.single().detail)
    }

    @Test
    fun `malformed model output degrades to a safe RESOLVED default without crashing`(): Unit = runBlocking {
        val reflector = CapabilityGapReflectorImpl(StubRouter(StubProvider("not json at all")))
        val result = reflector.reflect(SessionId("s1"), listOf(gap))
        assertEquals(1, result.size)
        assertEquals(CapabilityGapVerdict.RESOLVED, result.single().verdict)
    }

    @Test
    fun `a provider that throws degrades to a safe RESOLVED default without crashing`(): Unit = runBlocking {
        val throwingRouter = object : InferenceRouter {
            override suspend fun route(
                stageId: StageId,
                requiredCapabilities: Set<ModelCapability>,
            ): InferenceProvider = error("no provider available")
        }
        val reflector = CapabilityGapReflectorImpl(throwingRouter)
        val result = reflector.reflect(SessionId("s1"), listOf(gap))
        assertEquals(1, result.size)
        assertEquals(CapabilityGapVerdict.RESOLVED, result.single().verdict)
    }

    @Test
    fun `empty gap list never invokes inference`(): Unit = runBlocking {
        val router = object : InferenceRouter {
            override suspend fun route(
                stageId: StageId,
                requiredCapabilities: Set<ModelCapability>,
            ): InferenceProvider = error("must not be called for an empty gap list")
        }
        val result = CapabilityGapReflectorImpl(router).reflect(SessionId("s1"), emptyList())
        assertTrue(result.isEmpty())
    }
}
