package dev.rnett.gradle.mcp.mcp

import dev.rnett.gradle.mcp.fixtures.mcp.BaseMcpServerTest
import dev.rnett.gradle.mcp.gradle.BuildManager
import dev.rnett.gradle.mcp.gradle.GradleInvocationArguments
import dev.rnett.gradle.mcp.gradle.GradleProjectRoot
import dev.rnett.gradle.mcp.gradle.ProblemAggregation
import dev.rnett.gradle.mcp.gradle.ProblemId
import dev.rnett.gradle.mcp.gradle.ProblemSeverity
import dev.rnett.gradle.mcp.gradle.build.BuildComponentOutcome
import dev.rnett.gradle.mcp.gradle.build.BuildOutcome
import dev.rnett.gradle.mcp.gradle.build.BuildStatus
import dev.rnett.gradle.mcp.gradle.build.Failure
import dev.rnett.gradle.mcp.gradle.build.FailureId
import dev.rnett.gradle.mcp.gradle.build.FinishedBuild
import dev.rnett.gradle.mcp.gradle.build.GradleBuildScan
import dev.rnett.gradle.mcp.gradle.build.RunningBuild
import dev.rnett.gradle.mcp.gradle.build.TestResult
import dev.rnett.gradle.mcp.gradle.build.TestResults
import dev.rnett.gradle.mcp.tools.ToolNames
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.every
import io.mockk.mockk
import io.mockk.slot
import io.modelcontextprotocol.kotlin.sdk.types.TextContent
import kotlinx.coroutines.test.runTest
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
import org.junit.jupiter.api.AfterAll
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import kotlin.io.path.absolutePathString
import kotlin.test.assertContains
import kotlin.time.Clock
import kotlin.time.Duration.Companion.minutes
import kotlin.time.Duration.Companion.seconds

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class McpToolWorkflowsTest : BaseMcpServerTest() {

    @BeforeAll
    fun setupAll() = runTest(timeout = 2.minutes) {
        super.setup()
    }

    @AfterAll
    fun cleanupAll() = runTest(timeout = 2.minutes) {
        super.cleanup()
    }

    override fun setup() {
        // Handled by setupAll
    }

    override fun cleanup() {
        // Handled by cleanupAll
    }

    @BeforeEach
    fun setupTest() {
        // Optional: clear builds if needed, but synthetic builds use new IDs anyway
    }

    private fun syntheticBuildResult(): FinishedBuild {
        val args = GradleInvocationArguments.DEFAULT
        val id = buildManager.newId()
        val console = buildString {
            appendLine("Starting build for ${id}")
            appendLine("> Task :help")
            appendLine("BUILD SUCCESSFUL")
        }.trimEnd()
        val scans = emptyList<GradleBuildScan>()
        val testResults = TestResults(
            passed = setOf(
                TestResult("passes", "com.example.HelloTest", null, 0.1.seconds, failures = null, status = BuildComponentOutcome.SUCCESS, metadata = emptyMap(), attachments = emptyList())
            ),
            skipped = emptySet(),
            failed = setOf(
                TestResult(
                    "fails",
                    "com.example.HelloTest",
                    "Assertion failed",
                    0.2.seconds,
                    failures = listOf(
                        Failure(
                            FailureId("f1"),
                            message = "expected true to be false",
                            description = null,
                            causes = emptyList(),
                            problemAggregations = emptyMap()
                        )
                    ),
                    status = BuildComponentOutcome.FAILED,
                    metadata = emptyMap(),
                    attachments = emptyList()
                )
            )
        )
        val problems = mapOf(
            ProblemSeverity.ERROR to listOf(
                ProblemAggregation(
                    ProblemAggregation.ProblemDefinition(
                        id = ProblemId("test.group", "E001"),
                        displayName = "An example error",
                        severity = ProblemSeverity.ERROR,
                        documentationLink = null
                    ),
                    occurences = listOf(
                        ProblemAggregation.ProblemOccurence(
                            details = "Bad thing happened",
                            originLocations = emptyList(),
                            contextualLocations = emptyList(),
                            potentialSolutions = listOf("Do better")
                        )
                    )
                )
            )
        )
        return FinishedBuild(
            id = id,
            startTime = Clock.System.now(),
            args = args,
            consoleOutput = console,
            publishedScans = scans,
            testResults = testResults,
            problemAggregations = problems,
            outcome = BuildOutcome.Success,
            finishTime = Clock.System.now()
        )
    }

    @Test
    fun `inspect tools can read stored build`() = runTest {
        val result = syntheticBuildResult()
        val buildResults = server.koin.get<BuildManager>()
        buildResults.storeResult(result)

        // inspect_gradle_build (dashboard)
        val latestResult = server.client.callTool(ToolNames.QUERY_BUILD, emptyMap())
        assert(latestResult != null)

        // inspect_gradle_build (summary)
        val summary = server.client.callTool(
            ToolNames.QUERY_BUILD,
            mapOf("buildId" to result.id.toString(), "kind" to JsonPrimitive("DASHBOARD"))
        )
        assert(summary != null)

        // inspect_gradle_build (console)
        val console = server.client.callTool(
            ToolNames.QUERY_BUILD,
            mapOf(
                "buildId" to result.id.toString(),
                "kind" to JsonPrimitive("CONSOLE"),
                "pagination" to JsonObject(
                    mapOf(
                        "offset" to JsonPrimitive(0),
                        "limit" to JsonPrimitive(2)
                    )
                )
            )
        )
        assert(console != null)

        // inspect_gradle_build (tests)
        val tests = server.client.callTool(
            ToolNames.QUERY_BUILD,
            mapOf(
                "buildId" to result.id.toString(),
                "kind" to JsonPrimitive("TESTS"),
                "pagination" to JsonObject(mapOf("limit" to JsonPrimitive(10)))
            )
        )
        assert(tests != null)

        // inspect_gradle_build (problems)
        val problems = server.client.callTool(
            ToolNames.QUERY_BUILD,
            mapOf(
                "buildId" to result.id.toString(),
                "kind" to JsonPrimitive("PROBLEMS")
            )
        )
        assert(problems != null)
    }

    @Test
    fun `gradle_execute works with explicit projectRoot`() = runTest {
        val result = syntheticBuildResult()
        val runningBuild = mockk<RunningBuild>(relaxed = true) {
            coEvery { awaitFinished() } returns result
            every { id } returns result.id
        }
        val buildManager = server.koin.get<BuildManager>()
        buildManager.storeResult(result)
        coEvery {
            provider.runBuild(
                any(),
                any(),
                any(),
                any(),
                any(),
                any()
            )
        } returns runningBuild

        val args = mapOf(
            "projectRoot" to JsonPrimitive(tempDir.absolutePathString()),
            "commandLine" to JsonArray(listOf(JsonPrimitive("help")))
        )
        val call = server.client.callTool(ToolNames.GRADLE, args)
        assert(call != null)

        coVerify {
            provider.runBuild(
                GradleProjectRoot(tempDir.absolutePathString()),
                any(),
                any(),
                any(),
                any(),
                any()
            )
        }
    }

    @Test
    fun `consolidated build workflows`() = runTest {
        val buildId = buildManager.newId()
        val runningBuild = mockk<RunningBuild>(relaxed = true) {
            every { id } returns buildId
            every { status } returns BuildStatus.Running
            every { startTime } returns Clock.System.now()
            every { args } returns GradleInvocationArguments(additionalArguments = listOf("help"))
            every { logBuffer } returns StringBuffer("line1\nline2\n")
            every { stop() } returns Unit
            every { isRunning } returns true
            every { hasBuildFinished } returns false
            every { problemAggregations } returns emptyMap()
            every { testResults } returns TestResults(emptySet(), emptySet(), emptySet())
            every { publishedScans } returns emptyList()
            every { consoleOutput } returns "line1\nline2\n"
            every { consoleOutputLines } returns listOf("line1", "line2")
            every { problems } returns emptyList()
        }

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

        val buildManager = server.koin.get<BuildManager>()
        buildManager.registerBuild(runningBuild)

        // test gradle_execute background
        val runCall = server.client.callTool(
            ToolNames.GRADLE, mapOf(
                "projectRoot" to JsonPrimitive(tempDir.absolutePathString()),
                "commandLine" to JsonArray(listOf(JsonPrimitive("help"))),
                "background" to JsonPrimitive(true)
            )
        )
        assert(runCall != null)

        // test inspect_gradle_build (dashboard)
        val listCall = server.client.callTool(ToolNames.QUERY_BUILD, emptyMap())
        assert(listCall != null)
        val listText = listCall!!.content.filterIsInstance<TextContent>().joinToString { it.text ?: "" }
        assertContains(listText, buildId.toString())
        assertContains(listText, "BUILD IN PROGRESS")

        // test inspect_gradle_build with buildId
        val statusCall = server.client.callTool(ToolNames.QUERY_BUILD, mapOf("buildId" to buildId.toString()))
        assert(statusCall != null)
        val statusText = statusCall!!.content.filterIsInstance<TextContent>().joinToString { it.text ?: "" }
        assertContains(statusText, "BUILD IN PROGRESS")

        // test gradle_execute stop
        val stopCall = server.client.callTool(ToolNames.GRADLE, mapOf("stopBuildId" to buildId.toString()))
        assert(stopCall != null)
        coVerify { runningBuild.stop() }
    }

    @Test
    fun `gradle_execute uses arguments`() = runTest {
        val result = syntheticBuildResult()
        val runningBuild = mockk<RunningBuild>(relaxed = true) {
            coEvery { awaitFinished() } returns result
            every { id } returns result.id
        }

        val capturedArgs = slot<GradleInvocationArguments>()
        coEvery {
            provider.runBuild(
                any(),
                capture(capturedArgs),
                any(),
                any(),
                any(),
                any()
            )
        } returns runningBuild


        val args = mapOf(
            "projectRoot" to JsonPrimitive(tempDir.absolutePathString()),
            "commandLine" to JsonArray(listOf(JsonPrimitive(":help"), JsonPrimitive("--info"), JsonPrimitive("--stacktrace")))
        )
        server.client.callTool(ToolNames.GRADLE, args)

        assert(capturedArgs.captured.additionalArguments.contains(":help"))
        assert(capturedArgs.captured.additionalArguments.contains("--info"))
        assert(capturedArgs.captured.additionalArguments.contains("--stacktrace"))
    }
}
