package dev.rnett.gradle.mcp.tools

import dev.rnett.gradle.mcp.fixtures.mcp.BaseMcpServerTest
import dev.rnett.gradle.mcp.gradle.GradleInvocationArguments
import dev.rnett.gradle.mcp.gradle.GradleProjectRoot
import dev.rnett.gradle.mcp.gradle.build.BuildOutcome
import dev.rnett.gradle.mcp.gradle.build.FinishedBuild
import dev.rnett.gradle.mcp.gradle.build.RunningBuild
import dev.rnett.gradle.mcp.gradle.build.TestResults
import io.mockk.coEvery
import io.mockk.every
import io.mockk.mockk
import io.modelcontextprotocol.kotlin.sdk.types.CallToolResult
import io.modelcontextprotocol.kotlin.sdk.types.TextContent
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import kotlin.time.Clock

class ReplToolTest : BaseMcpServerTest() {


    @Test
    fun `start command with projectRoot works`() = runTest {
        val projectPath = ":app"
        val sourceSet = "main"
        val javaExec = "java"
        val customRoot = tempDir.resolve("custom")
        java.io.File(customRoot.toUri()).mkdirs()
        java.io.File(customRoot.resolve("gradlew").toUri()).createNewFile()

        val consoleOutput = """
            [gradle-mcp] [repl-env] classpath=cp1;cp2
            [gradle-mcp] [repl-env] javaExecutable=$javaExec
        """.trimIndent()

        val finishedBuild = mockk<FinishedBuild>(relaxed = true)
        every { finishedBuild.outcome } returns BuildOutcome.Success
        every { finishedBuild.id } returns buildManager.newId()
        every { finishedBuild.consoleOutput } returns consoleOutput

        val runningBuild = mockk<RunningBuild>(relaxed = true)
        coEvery { runningBuild.awaitFinished() } returns finishedBuild

        every { provider.runBuild(any(), any(), any(), any(), any(), any()) } returns runningBuild

        val response = server.client.callTool(
            ToolNames.REPL, mapOf(
                "command" to "start",
                "projectRoot" to customRoot.toString(),
                "projectPath" to projectPath,
                "sourceSet" to sourceSet
            )
        ) as CallToolResult

        val text = (response.content.first() as TextContent).text
        assert(text!!.startsWith("REPL session started with ID: "))

        // Verify provider was called with the custom root
        io.mockk.verify { provider.runBuild(GradleProjectRoot(customRoot.toString()), any(), any(), any(), any(), any()) }
    }

    @Test
    fun `start command runs build and starts session`() = runTest {
        val projectPath = ":app"
        val sourceSet = "main"
        val javaExec = "java"

        val consoleOutput = """
            [gradle-mcp] [repl-env] classpath=cp1;cp2
            [gradle-mcp] [repl-env] javaExecutable=$javaExec
            [gradle-mcp] [repl-env] pluginsClasspath=p1;p2
            [gradle-mcp] [repl-env] compilerArgs=a1;a2
        """.trimIndent()

        val finishedBuild = mockk<FinishedBuild>(relaxed = true)
        every { finishedBuild.outcome } returns BuildOutcome.Success
        every { finishedBuild.id } returns buildManager.newId()
        every { finishedBuild.consoleOutput } returns consoleOutput

        val runningBuild = mockk<RunningBuild>(relaxed = true)
        coEvery { runningBuild.awaitFinished() } returns finishedBuild

        every { provider.runBuild(any(), any(), any(), any(), any(), any()) } returns runningBuild

        val response = server.client.callTool(
            ToolNames.REPL, mapOf(
                "command" to "start",
                "projectRoot" to tempDir.toString(),
                "projectPath" to projectPath,
                "sourceSet" to sourceSet
            )
        ) as CallToolResult

        val text = (response.content.first() as TextContent).text
        assert(text!!.startsWith("REPL session started with ID: "))
    }

    @Test
    fun `stop command works`() = runTest {
        // Start a session first (we need to mock the build again)
        val javaExec = "java"
        val consoleOutput = "[gradle-mcp] [repl-env] javaExecutable=$javaExec"
        val finishedBuild = mockk<FinishedBuild>(relaxed = true)
        every { finishedBuild.outcome } returns BuildOutcome.Success
        every { finishedBuild.id } returns buildManager.newId()
        every { finishedBuild.consoleOutput } returns consoleOutput

        val runningBuild = mockk<RunningBuild>(relaxed = true)
        coEvery { runningBuild.awaitFinished() } returns finishedBuild

        every { provider.runBuild(any(), any(), any(), any(), any(), any()) } returns runningBuild

        server.client.callTool(
            ToolNames.REPL, mapOf(
                "command" to "start",
                "projectRoot" to tempDir.toString(),
                "projectPath" to ":app",
                "sourceSet" to "main"
            )
        )

        val response = server.client.callTool(ToolNames.REPL, mapOf("command" to "stop")) as CallToolResult
        assert((response.content.first() as TextContent).text == "REPL session stopped.")
    }

    @Test
    fun `snippet without session returns error`() = runTest {
        val response = server.client.callTool(
            ToolNames.REPL, mapOf(
                "command" to "run",
                "code" to "val x = 1"
            )
        ) as CallToolResult
        assert(response.isError == true)
        val text = (response.content.first() as TextContent).text
        assert(text == "No active REPL session. Start one with command 'start'.")
    }

    @Test
    fun `start command with missing javaExecutable returns graceful error`() = runTest {
        val projectPath = ":app"
        val sourceSet = "main"

        val consoleOutput = """
            [gradle-mcp] [repl-env] projectRoot=C:\path\to\project
            [gradle-mcp] [repl-env] classpath=cp1;cp2
        """.trimIndent()

        val finishedBuild = mockk<FinishedBuild>(relaxed = true)
        every { finishedBuild.outcome } returns BuildOutcome.Success
        every { finishedBuild.id } returns buildManager.newId()
        every { finishedBuild.consoleOutput } returns consoleOutput

        val runningBuild = mockk<RunningBuild>(relaxed = true)
        coEvery { runningBuild.awaitFinished() } returns finishedBuild

        every { provider.runBuild(any(), any(), any(), any(), any(), any()) } returns runningBuild

        val response = server.client.callTool(
            ToolNames.REPL, mapOf(
                "command" to "start",
                "projectRoot" to tempDir.toString(),
                "projectPath" to projectPath,
                "sourceSet" to sourceSet
            )
        ) as CallToolResult

        assert(response.isError == true)
        val text = (response.content.first() as TextContent).text
        assert(text!!.contains("No JVM target available"))
    }

    @Test
    fun `start command with non-jvm source set returns graceful error`() = runTest {
        val projectPath = ":app"
        val sourceSet = "commonMain"

        val finishedBuild = FinishedBuild(
            id = buildManager.newId(),
            startTime = Clock.System.now(),
            args = GradleInvocationArguments(additionalArguments = emptyList()),
            consoleOutput = "FAILURE: Build failed with an exception.\n\n* What went wrong:\nExecution failed for task ':app:resolveReplEnvironment'.\n> SourceSet 'commonMain' found in project ':app', but it does not appear to be a JVM source set. REPL is only supported for JVM source sets.",
            publishedScans = emptyList(),
            testResults = TestResults(emptySet<dev.rnett.gradle.mcp.gradle.build.TestResult>(), emptySet<dev.rnett.gradle.mcp.gradle.build.TestResult>(), emptySet<dev.rnett.gradle.mcp.gradle.build.TestResult>()),
            problemAggregations = emptyMap(),
            outcome = BuildOutcome.Failed(emptyList()),
            finishTime = Clock.System.now()
        )

        val runningBuild = mockk<RunningBuild>(relaxed = true)
        coEvery { runningBuild.awaitFinished() } returns finishedBuild

        every { provider.runBuild(any(), any(), any(), any(), any(), any()) } returns runningBuild

        val response = server.client.callTool(
            ToolNames.REPL, mapOf(
                "command" to "start",
                "projectRoot" to tempDir.toString(),
                "projectPath" to projectPath,
                "sourceSet" to sourceSet
            )
        ) as CallToolResult

        assert(response.isError == true)
        val text = (response.content.first() as TextContent).text
        assert(text!!.contains("Failed to resolve REPL environment because Gradle task failed"))
        assert(text.contains("does not appear to be a JVM source set"))
    }
}
