package com.correx.apps.server.memory

import com.correx.core.events.types.SessionId
import com.correx.core.events.types.StageId
import com.correx.core.inference.Embedder
import com.correx.core.talkie.l3.L3Hit
import com.correx.core.talkie.l3.L3MemoryEntry
import com.correx.core.talkie.l3.L3MemoryStore
import com.correx.core.talkie.l3.L3Query
import kotlinx.coroutines.runBlocking
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNull
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
import java.util.UUID

private class ContradictionOnesEmbedder(override val dimension: Int = 8) : Embedder {
    override suspend fun embed(text: String): FloatArray = FloatArray(dimension) { 1f }
}

/** Returns canned hits regardless of the query vector, so scores can be controlled precisely. */
private class CannedL3MemoryStore(private val hits: List<L3Hit>) : L3MemoryStore {
    override suspend fun store(entry: L3MemoryEntry) = Unit
    override suspend fun query(query: L3Query): List<L3Hit> = hits.sortedByDescending { it.score }.take(query.k)
    override suspend fun existsByTurnIdPrefix(prefix: String): Boolean = hits.any { it.entry.turnId.startsWith(prefix) }
    override suspend fun close() = Unit
}

class ArchitectContradictionCheckerTest {

    private val newSession = SessionId("new-session")
    private val priorSession = SessionId("prior-session")
    private val stageId = StageId("architect")

    private fun hit(text: String, score: Float, turnId: String, sessionId: SessionId = priorSession) =
        L3Hit(
            entry = L3MemoryEntry(
                id = UUID.randomUUID().toString(),
                sessionId = sessionId,
                turnId = turnId,
                text = text,
                vector = FloatArray(8) { 1f },
                timestampMs = 0L,
            ),
            score = score,
        )

    @Test
    fun `flags a related prior decision above threshold`() = runBlocking {
        val store = CannedL3MemoryStore(
            listOf(hit("Decided to use SQLite for the event store.", score = 0.9f, turnId = "project:/repo")),
        )
        val checker = ArchitectContradictionChecker(ContradictionOnesEmbedder(), store)

        val flag = checker.check(newSession, stageId, "Use Postgres for the event store.", "/repo")

        assertTrue(flag != null, "expected a flag")
        assertEquals(newSession, flag!!.sessionId)
        assertEquals(stageId, flag.stageId)
        assertEquals("Use Postgres for the event store.", flag.decisionSummary)
        assertEquals(1, flag.related.size)
        val related = flag.related.single()
        assertEquals("Decided to use SQLite for the event store.", related.summary)
        assertEquals("project:/repo", related.source)
        assertEquals(0.9, related.score, 1e-6)
    }

    @Test
    fun `returns null when there are no hits`() = runBlocking {
        val checker = ArchitectContradictionChecker(ContradictionOnesEmbedder(), CannedL3MemoryStore(emptyList()))

        assertNull(checker.check(newSession, stageId, "Use Postgres for the event store.", "/repo"))
    }

    @Test
    fun `returns null when all hits are below threshold`() = runBlocking {
        val store = CannedL3MemoryStore(
            listOf(hit("Loosely related prior note.", score = 0.5f, turnId = "project:/repo")),
        )
        val checker = ArchitectContradictionChecker(ContradictionOnesEmbedder(), store, scoreThreshold = 0.75)

        assertNull(checker.check(newSession, stageId, "Use Postgres for the event store.", "/repo"))
    }

    @Test
    fun `filters out hits outside the decision namespace`() = runBlocking {
        val store = CannedL3MemoryStore(
            listOf(
                // Above threshold but a repo-map entry, not a decision — must be excluded.
                hit("Foo.kt: ClassA, funcB", score = 0.95f, turnId = "repomap:v2:/repo:abc"),
            ),
        )
        val checker = ArchitectContradictionChecker(ContradictionOnesEmbedder(), store)

        assertNull(checker.check(newSession, stageId, "Use Postgres for the event store.", "/repo"))
    }

    @Test
    fun `filters out decisions from another workspace`() = runBlocking {
        val store = CannedL3MemoryStore(
            listOf(hit("Other workspace decision.", score = 0.95f, turnId = "project:/other-repo")),
        )
        val checker = ArchitectContradictionChecker(ContradictionOnesEmbedder(), store)

        assertNull(checker.check(newSession, stageId, "Use Postgres for the event store.", "/repo"))
    }

    @Test
    fun `filters out the architect's own in-flight session`() = runBlocking {
        val store = CannedL3MemoryStore(
            listOf(hit("Same-session decision.", score = 0.95f, turnId = "project:/repo", sessionId = newSession)),
        )
        val checker = ArchitectContradictionChecker(ContradictionOnesEmbedder(), store)

        assertNull(checker.check(newSession, stageId, "Use Postgres for the event store.", "/repo"))
    }
}
