package com.correx.infrastructure.tools.web

import com.correx.core.approvals.Tier
import com.correx.core.events.events.ToolRequest
import com.correx.core.tools.contract.ToolCapability
import com.correx.core.tools.contract.ToolResult
import com.correx.core.tools.contract.ValidationResult
import com.correx.core.utils.TypeId
import io.ktor.client.HttpClient
import io.ktor.client.engine.mock.MockEngine
import io.ktor.client.engine.mock.respond
import io.ktor.http.HttpStatusCode
import io.ktor.http.headersOf
import io.ktor.http.HttpHeaders
import kotlinx.coroutines.runBlocking
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertInstanceOf
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test

class WebSearchToolTest {

    private var lastRequestedUrl: String = ""

    private fun toolFor(
        json: String,
        status: HttpStatusCode = HttpStatusCode.OK,
        maxResults: Int = 8,
    ): WebSearchTool {
        val engine = MockEngine { request ->
            lastRequestedUrl = request.url.toString()
            respond(content = json, status = status, headers = headersOf(HttpHeaders.ContentType, "application/json"))
        }
        return WebSearchTool(HttpClient(engine), searxngBaseUrl = "http://searx.local:8888", maxResults = maxResults)
    }

    private fun request(query: String?) = ToolRequest(
        invocationId = TypeId("inv-1"),
        sessionId = TypeId("s-1"),
        stageId = TypeId("stage-1"),
        toolName = "web_search",
        parameters = if (query == null) emptyMap() else mapOf("query" to query),
    )

    private fun search(tool: WebSearchTool, query: String = "kotlin coroutines") =
        runBlocking { tool.execute(request(query)) }

    private val sample = """
        {"results":[
          {"url":"https://a.com/1","title":"First","content":"about one"},
          {"url":"https://b.com/2","title":"Second","content":"about two"}
        ]}
    """.trimIndent()

    @Test
    fun `is tier one and network capable`() {
        val tool = toolFor(sample)
        assertEquals(Tier.T1, tool.tier)
        assertTrue(ToolCapability.NETWORK_ACCESS in tool.requiredCapabilities)
    }

    @Test
    fun `queries searxng json endpoint with encoded query`() {
        search(toolFor(sample), "a b&c")
        assertTrue(lastRequestedUrl.contains("/search?q="), lastRequestedUrl)
        assertTrue(lastRequestedUrl.contains("format=json"), lastRequestedUrl)
        assertTrue(lastRequestedUrl.contains("a+b%26c"), "query not url-encoded: $lastRequestedUrl")
    }

    @Test
    fun `renders results and exposes urls in metadata`() {
        val success = assertInstanceOf(ToolResult.Success::class.java, search(toolFor(sample)))
        assertTrue(success.output.contains("First"))
        assertTrue(success.output.contains("https://a.com/1"))
        assertEquals("2", success.metadata["result_count"])
        assertEquals("https://a.com/1\nhttps://b.com/2", success.metadata["urls"])
    }

    @Test
    fun `respects max results cap`() {
        val success = assertInstanceOf(ToolResult.Success::class.java, search(toolFor(sample, maxResults = 1)))
        assertEquals("1", success.metadata["result_count"])
        assertEquals("https://a.com/1", success.metadata["urls"])
    }

    @Test
    fun `empty results render a no-results message`() {
        val success = assertInstanceOf(ToolResult.Success::class.java, search(toolFor("""{"results":[]}""")))
        assertTrue(success.output.contains("No results"))
        assertEquals("0", success.metadata["result_count"])
    }

    @Test
    fun `searxng error is a recoverable failure`() {
        val result = search(toolFor("err", status = HttpStatusCode.BadGateway))
        val failure = assertInstanceOf(ToolResult.Failure::class.java, result)
        assertTrue(failure.recoverable)
    }

    @Test
    fun `blank query is rejected`() {
        assertInstanceOf(ValidationResult.Invalid::class.java, toolFor(sample).validateRequest(request("  ")))
    }
}
