package com.correx.core.tasks

import com.correx.core.events.types.ProjectId
import com.correx.core.events.types.TaskId
import com.correx.core.events.types.TaskLinkType
import com.correx.core.events.types.TaskNoteAuthor
import com.correx.core.events.types.TaskTargetKind
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

class TaskContextAssemblerTest {

    private val store = InMemoryEventStore()
    private val service = TaskService(store)
    private val assembler = TaskContextAssembler(service)
    private val project = ProjectId("auth")

    @Test
    fun `bundle resolves linked tasks as dependencies and keeps non-task links raw`() = runBlocking {
        val dep = service.createTask(project, "Design auth model", "model")
        service.claim(dep.taskId, "claude-opus")
        service.submitForReview(dep.taskId)
        service.complete(dep.taskId) // dep is DONE

        val task = service.createTask(
            project,
            title = "Implement JWT refresh",
            goal = "users stay authenticated",
            acceptanceCriteria = listOf("refresh endpoint exists"),
            affectedPaths = listOf("backend/auth/**"),
        )
        service.link(task.taskId, dep.taskId.value, TaskLinkType.DEPENDS_ON, TaskTargetKind.TASK)
        // DOC-tagged, but this assembler has no document resolver → stays a raw related link
        service.link(task.taskId, "adr-7", TaskLinkType.IMPLEMENTS, TaskTargetKind.DOC)
        service.addNote(task.taskId, TaskNoteAuthor.AGENT, "kickoff")

        val bundle = assembler.assemble(task.taskId)!!

        assertEquals("auth-2", bundle.id)
        assertEquals("users stay authenticated", bundle.goal)
        assertEquals(listOf("refresh endpoint exists"), bundle.acceptanceCriteria)
        assertEquals(listOf("backend/auth/**"), bundle.relevantFiles)

        val dependency = bundle.dependencies.single()
        assertEquals("auth-1", dependency.id)
        assertEquals("DONE", dependency.status)
        assertEquals("DEPENDS_ON", dependency.link)

        assertEquals(RelatedLink("adr-7", "IMPLEMENTS"), bundle.relatedLinks.single())
        assertEquals("kickoff", bundle.notes.single().body)
    }

    @Test
    fun `bundle flags an unfinished dependency as a blocker and clears it when done`() = runBlocking {
        val dep = service.createTask(project, "Design auth model", "model") // auth-1
        service.claim(dep.taskId, "claude-opus")                           // auth-1 IN_PROGRESS
        val task = service.createTask(project, "Implement JWT refresh", "auth") // auth-2
        service.link(task.taskId, dep.taskId.value, TaskLinkType.DEPENDS_ON, TaskTargetKind.TASK)

        val blocked = assembler.assemble(task.taskId)!!
        assertTrue(blocked.blocked)
        assertEquals("auth-1", blocked.blockedBy.single().id)
        assertEquals("IN_PROGRESS", blocked.blockedBy.single().status)
        assertEquals("DEPENDS_ON", blocked.blockedBy.single().link)
        assertTrue(blocked.render().contains("BLOCKED"))

        service.submitForReview(dep.taskId)
        service.complete(dep.taskId)
        val cleared = assembler.assemble(task.taskId)!!
        assertTrue(!cleared.blocked)
        assertTrue(cleared.blockedBy.isEmpty())
    }

    @Test
    fun `render produces a compact labelled bundle`() = runBlocking {
        val task = service.createTask(project, "JWT refresh", "stay authed", listOf("rotates"))
        val text = assembler.assemble(task.taskId)!!.render()

        assertTrue(text.startsWith("task auth-1 [TODO] JWT refresh"))
        assertTrue(text.contains("goal: stay authed"))
        assertTrue(text.contains("- rotates"))
    }

    @Test
    fun `assemble returns null for an unknown task`() = runBlocking {
        assertNull(assembler.assemble(TaskId("auth-999")))
    }

    @Test
    fun `bundle is enriched with knowledge retrieved over the goal`() = runBlocking {
        val captured = mutableListOf<String>()
        val fakeRetriever = object : TaskKnowledgeRetriever {
            override suspend fun retrieve(query: String, limit: Int): List<KnowledgeHit> {
                captured += query
                return listOf(KnowledgeHit("backend/auth/Jwt.kt", "fun refresh(token)", 0.91))
            }
        }
        val enriched = TaskContextAssembler(service, fakeRetriever)
        val task = service.createTask(project, "JWT refresh", "users stay authenticated")

        val bundle = enriched.assemble(task.taskId)!!

        assertEquals(listOf("JWT refresh users stay authenticated"), captured)
        assertEquals("backend/auth/Jwt.kt", bundle.relevantKnowledge.single().source)
        assertTrue(bundle.render().contains("relevant_knowledge"))
    }

    @Test
    fun `linked docs are resolved inline and dropped from raw related links`() = runBlocking {
        val docs = object : TaskDocumentResolver {
            override suspend fun resolve(targetId: String): ResolvedDocument? =
                if (targetId == "adr-7") {
                    ResolvedDocument("ADR 7: use redis", "docs/decisions/adr-0007-redis.md", "faster invalidation")
                } else {
                    null
                }
        }
        val enriched = TaskContextAssembler(service, documentResolver = docs)
        val task = service.createTask(project, "JWT refresh", "auth")
        service.link(task.taskId, "adr-7", TaskLinkType.IMPLEMENTS, TaskTargetKind.DOC) // resolves to a doc
        service.link(task.taskId, "ext-123", TaskLinkType.RELATES_TO, TaskTargetKind.ARTIFACT) // stays raw

        val bundle = enriched.assemble(task.taskId)!!

        val doc = bundle.documents.single()
        assertEquals("adr-7", doc.targetId)
        assertEquals("ADR 7: use redis", doc.title)
        assertEquals("IMPLEMENTS", doc.type)
        assertEquals(RelatedLink("ext-123", "RELATES_TO"), bundle.relatedLinks.single())
        assertTrue(bundle.render().contains("documents:"))
    }

    @Test
    fun `retrieval failure is swallowed and the bundle still returns`() = runBlocking {
        val flaky = object : TaskKnowledgeRetriever {
            override suspend fun retrieve(query: String, limit: Int): List<KnowledgeHit> =
                error("embedder offline")
        }
        val task = service.createTask(project, "JWT refresh", "stay authed")

        val bundle = TaskContextAssembler(service, flaky).assemble(task.taskId)!!

        assertTrue(bundle.relevantKnowledge.isEmpty())
        assertEquals("JWT refresh", bundle.title)
    }

    @Test
    fun `linked artifacts and sessions are resolved inline and unresolved ones stay raw`() = runBlocking {
        val artifacts = object : TaskArtifactResolver {
            override suspend fun resolve(targetId: String): ResolvedArtifact? =
                if (targetId == "art-1") ResolvedArtifact("architect", "sess-9", "approach: redis") else null
        }
        val sessions = object : TaskSessionResolver {
            override suspend fun resolve(targetId: String): ResolvedSession? =
                if (targetId == "sess-9") ResolvedSession("COMPLETED", "build auth", "role_pipeline") else null
        }
        val enriched = TaskContextAssembler(service, artifactResolver = artifacts, sessionResolver = sessions)
        val task = service.createTask(project, "JWT refresh", "auth")
        service.link(task.taskId, "art-1", TaskLinkType.PRODUCED, TaskTargetKind.ARTIFACT)
        service.link(task.taskId, "sess-9", TaskLinkType.CONTEXT, TaskTargetKind.SESSION)
        service.link(task.taskId, "art-missing", TaskLinkType.RELATES_TO, TaskTargetKind.ARTIFACT) // unresolved → raw

        val bundle = enriched.assemble(task.taskId)!!

        val artifact = bundle.artifacts.single()
        assertEquals("art-1", artifact.targetId)
        assertEquals("architect", artifact.stage)
        assertEquals("sess-9", artifact.sessionId)
        assertEquals("PRODUCED", artifact.type)

        val session = bundle.sessions.single()
        assertEquals("sess-9", session.targetId)
        assertEquals("COMPLETED", session.status)
        assertEquals("build auth", session.intent)
        assertEquals("CONTEXT", session.type)

        assertEquals(RelatedLink("art-missing", "RELATES_TO"), bundle.relatedLinks.single())
        assertTrue(bundle.render().contains("artifacts:"))
        assertTrue(bundle.render().contains("sessions:"))
    }
}
