package com.optix.android.tools.engine

import com.optix.android.domain.model.AppSettings
import com.optix.android.domain.model.ToolCategory
import com.optix.android.domain.model.ToolEvent
import com.optix.android.domain.model.ToolMetadata
import com.optix.android.domain.model.ToolParam
import com.optix.android.domain.repository.SettingsRepository
import com.optix.android.tools.registry.BaseTool
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertTrue
import org.junit.Test

/**
 * Fake tool whose validate() passes and execute() emits Done.
 */
private class FakeTool(
    private val toolName: String,
    requiredParams: List<String> = emptyList(),
) : BaseTool() {
    override val metadata: ToolMetadata = ToolMetadata(
        name = toolName,
        displayName = toolName,
        description = "fake",
        category = ToolCategory.NETWORK_SCANNING,
        parameters = requiredParams.map { ToolParam(it, com.optix.android.domain.model.ParamType.STRING, "p") },
    )

    var executed = false

    override suspend fun execute(params: Map<String, Any>, emit: suspend (ToolEvent) -> Unit) {
        executed = true
        emit(ToolEvent.Done)
    }
}

/**
 * Fake tool whose validate() always returns an error, so execute() should never run.
 */
private class FailingTool(
    private val toolName: String,
    private val errorMessage: String,
) : BaseTool() {
    override val metadata: ToolMetadata = ToolMetadata(
        name = toolName,
        displayName = toolName,
        description = "fake failing",
        category = ToolCategory.NETWORK_SCANNING,
        parameters = emptyList(),
    )

    var executed = false

    override fun validate(params: Map<String, Any>): List<String> = listOf(errorMessage)

    override suspend fun execute(params: Map<String, Any>, emit: suspend (ToolEvent) -> Unit) {
        executed = true
        emit(ToolEvent.Done)
    }
}

/**
 * Minimal SettingsRepository fake returning a default AppSettings.
 */
private class FakeSettingsRepository : SettingsRepository {
    override fun getAiConfig() = flow { emit(com.optix.android.domain.model.AiProviderConfig()) }
    override suspend fun saveAiConfig(config: com.optix.android.domain.model.AiProviderConfig) {}
    override fun getAppSettings() = flow { emit(AppSettings()) }
    override suspend fun saveAppSettings(settings: AppSettings) {}
    override suspend fun toggleFavorite(toolId: String) {}
    override fun getNetworkSettings() = flow { emit(com.optix.android.domain.model.NetworkSettings()) }
    override suspend fun saveNetworkSettings(settings: com.optix.android.domain.model.NetworkSettings) {}
    override fun getOsintApiKeys() = flow { emit(emptyMap<String, String>()) }
    override suspend fun saveOsintApiKey(service: String, key: String) {}
}

/**
 * EnvironmentManager is a concrete class; subclass it with no-ops for unit tests.
 */
private class FakeEnvironmentManager : EnvironmentManager() {
    override suspend fun isRootAvailable(): Boolean = false
    override fun isTermuxInstalled(): Boolean = false
}

class ToolExecutionEngineTest {

    private fun engine(): ToolExecutionEngine =
        ToolExecutionEngine(FakeSettingsRepository(), ShellExecutor(FakeEnvironmentManager()))

    @Test
    fun run_emitsDone_forSuccessfulTool() = runTest {
        val tool = FakeTool("fake_tool")
        val events = engine().run(tool, emptyMap()).toList()

        assertTrue(tool.executed)
        assertEquals(ToolEvent.Done, events.last())
        assertNotNull(events.firstOrNull { it is ToolEvent.Done })
    }

    @Test
    fun run_emitsErrorThenDone_withoutExecute_whenValidationFails() = runTest {
        val tool = FailingTool("failing_tool", "required 'x' is missing")
        val events = engine().run(tool, emptyMap()).toList()

        assertFalse(tool.executed)
        val errorEvent = events.firstOrNull { it is ToolEvent.Error } as? ToolEvent.Error
        assertNotNull(errorEvent)
        assertEquals("required 'x' is missing", errorEvent?.message)
        assertEquals(ToolEvent.Done, events.last())
    }
}
