package dev.rnett.gradle.mcp.tools

import dev.rnett.gradle.mcp.fixtures.gradle.GradleProjectFixture
import dev.rnett.gradle.mcp.fixtures.gradle.testGradleProject
import dev.rnett.gradle.mcp.fixtures.gradle.withTestGradleDefaults
import dev.rnett.gradle.mcp.fixtures.mcp.BaseMcpServerTest
import dev.rnett.gradle.mcp.gradle.DefaultGradleProvider
import dev.rnett.gradle.mcp.gradle.GradleProvider
import io.modelcontextprotocol.kotlin.sdk.types.CallToolRequest
import io.modelcontextprotocol.kotlin.sdk.types.CallToolRequestParams
import io.modelcontextprotocol.kotlin.sdk.types.CallToolResult
import io.modelcontextprotocol.kotlin.sdk.types.Method
import io.modelcontextprotocol.kotlin.sdk.types.ProgressNotificationParams
import io.modelcontextprotocol.kotlin.sdk.types.RequestMeta
import io.modelcontextprotocol.kotlin.sdk.types.ProgressNotification
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.test.runTest
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.koin.core.scope.Scope
import java.util.concurrent.ConcurrentLinkedQueue
import kotlin.io.path.absolutePathString
import kotlin.test.assertTrue
import kotlin.time.Duration.Companion.seconds

class GradleProgressIntegrationTest : BaseMcpServerTest() {

    companion object {
        private const val PHASE_CONFIGURING = "[CONFIGURING]"
        private const val PHASE_EXECUTING = "[EXECUTING]"
    }

    private lateinit var _project: GradleProjectFixture

    override fun Scope.createProvider(): GradleProvider {
        return DefaultGradleProvider(
            buildManager = get()
        ).withTestGradleDefaults(
            additionalSystemProps = mapOf("org.gradle.configuration-cache" to "false")
        )
    }


    @BeforeEach
    override fun setup() = runTest {
        System.setProperty("gradle.mcp.test.disableSampling", "true")
        _project = testGradleProject() {
            buildScript("Thread.sleep(100)\n".repeat(20))
        }
        super.setup()
    }

    @AfterEach
    override fun cleanup() = runTest {
        System.clearProperty("gradle.mcp.test.disableSampling")
        _project.close()
        super.cleanup()
    }

    @Test
    fun `gradle build emits progress percentages and phase prefixes`() = runTest(timeout = 120.seconds) {
        val progressNotifications = ConcurrentLinkedQueue<ProgressNotificationParams>()

        server.client.setNotificationHandler<ProgressNotification>(Method.Defined.NotificationsProgress) { notification: ProgressNotification ->
            progressNotifications.add(notification.params)
            CompletableDeferred(Unit)
        }

        // Run build
        server.client.request<CallToolResult>(
            CallToolRequest(
                CallToolRequestParams(
                    name = ToolNames.GRADLE,
                    arguments = buildJsonObject {
                        put("commandLine", JsonArray(listOf(JsonPrimitive("help"))))
                        put("projectRoot", _project.path().absolutePathString())
                    },
                    meta = RequestMeta(buildJsonObject {
                        put("progressToken", "test-token")
                    })
                )
            )
        )

        val notifications = progressNotifications.toList()

        // Verify we got some progress notifications
        assertTrue(notifications.isNotEmpty(), "Should have received progress notifications")

        // Verify phase prefixes - at least one notification should have it
        val hasConfiguring = notifications.any { it.message?.contains(PHASE_CONFIGURING) == true }
        // Note: transient messages like [CONFIGURING] might be sampled out in extremely fast builds,
        // but typically at least one notification will capture it.
        assertTrue(hasConfiguring, "Should have seen $PHASE_CONFIGURING phase (messages: ${notifications.map { it.message }})")

        // Verify percentage values (0.0 to 1.0)
        notifications.forEach { params ->
            if (params.total != null && params.total!! > 0) {
                assertTrue(params.progress >= 0.0 && params.progress <= params.total!!, "Progress ${params.progress} should be between 0 and ${params.total} (message: ${params.message})")
            }
        }
    }

    @Test
    fun `gradle build with multiple tasks shows descriptive progress`() = runTest(timeout = 120.seconds) {
        val progressNotifications = ConcurrentLinkedQueue<ProgressNotificationParams>()

        server.client.setNotificationHandler<ProgressNotification>(Method.Defined.NotificationsProgress) { notification: ProgressNotification ->
            progressNotifications.add(notification.params)
            CompletableDeferred(Unit)
        }

        // Run build with two tasks
        server.client.request<CallToolResult>(
            CallToolRequest(
                CallToolRequestParams(
                    name = ToolNames.GRADLE,
                    arguments = buildJsonObject {
                        put("commandLine", JsonArray(listOf(JsonPrimitive("help"), JsonPrimitive("tasks"))))
                        put("projectRoot", _project.path().absolutePathString())
                    },
                    meta = RequestMeta(buildJsonObject {
                        put("progressToken", "test-token")
                    })
                )
            )
        )

        val notifications = progressNotifications.toList()

        // Verify we got some progress notifications
        assertTrue(notifications.isNotEmpty(), "Should have received progress notifications")

        // Verify descriptive progress - we should see either active tasks or finished tasks
        val hasTaskDetail = notifications.any {
            val msg = it.message ?: ""
            msg.contains(":help") || msg.contains(":tasks") || msg.contains("other task") || msg.contains("Finished")
        }
        assertTrue(hasTaskDetail, "Should have seen task-related details in progress (messages: ${notifications.map { it.message }})")

        val hasExecuting = notifications.any { it.message?.contains(PHASE_EXECUTING) == true }
        assertTrue(hasExecuting, "Should have seen $PHASE_EXECUTING phase (messages: ${notifications.map { it.message }})")
    }

    @Test
    fun `gradle configuration phase shows detailed progress`() = runTest(timeout = 120.seconds) {
        val progressNotifications = ConcurrentLinkedQueue<ProgressNotificationParams>()

        server.client.setNotificationHandler(Method.Defined.NotificationsProgress) { notification: ProgressNotification ->
            progressNotifications.add(notification.params)
            CompletableDeferred(Unit)
        }

        // Run build
        server.client.request<CallToolResult>(
            CallToolRequest(
                CallToolRequestParams(
                    name = ToolNames.GRADLE,
                    arguments = buildJsonObject {
                        put("commandLine", JsonArray(listOf(JsonPrimitive("help"))))
                        put("projectRoot", _project.path().absolutePathString())
                    },
                    meta = RequestMeta(buildJsonObject {
                        put("progressToken", "test-token")
                    })
                )
            )
        )

        val notifications = progressNotifications.toList()

        // Check for specific configuration messages or just that we saw the configuration phase
        // Transient messages like "Configure project :" might be sampled out, but we should at least see [CONFIGURING]
        val hasConfiguring = notifications.any {
            it.message?.contains(PHASE_CONFIGURING) == true
        }
        assertTrue(hasConfiguring, "Should have seen configuration phase progress (messages: ${notifications.map { it.message }})")
    }
}
