package com.optix.android.tools.engine

import com.optix.android.domain.model.ToolEvent
import com.optix.android.domain.repository.SettingsRepository
import com.optix.android.tools.registry.BaseTool
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.withTimeout
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Singleton

/**
 * Wraps tool execution in a [Flow] with:
 *  - Per-tool timeout enforcement
 *  - Cooperative cancellation
 *  - Structured error handling (timeout vs. other exceptions)
 *  - All work dispatched on [Dispatchers.IO]
 */
@Singleton
class ToolExecutionEngine @Inject constructor(
    private val settingsRepository: SettingsRepository,
    private val shellExecutor: ShellExecutor,
    private val environmentManager: EnvironmentManager,
) {

    fun run(tool: BaseTool, params: Map<String, Any>): Flow<ToolEvent> = channelFlow {
        // Pre-execution validation — fail fast with clear messages
        val validationErrors = tool.validate(params)
        if (validationErrors.isNotEmpty()) {
            validationErrors.forEach { send(ToolEvent.Error(it)) }
            send(ToolEvent.Done)
            return@channelFlow
        }

        // Capability pre-check — refuse to run gracefully instead of crashing
        // when a required capability (root / Termux) is absent.
        val meta = tool.metadata
        if (meta.requiresRoot && !environmentManager.isRootAvailable()) {
            send(
                ToolEvent.CapabilityMissing(
                    toolName = meta.name,
                    reason = "'$tool' requires root access, which is not available on this device.",
                    missingCapability = ToolEvent.MissingCapability.ROOT,
                )
            )
            send(ToolEvent.Done)
            return@channelFlow
        }
        if (meta.requiresTermux && !environmentManager.isTermuxInstalled()) {
            send(
                ToolEvent.CapabilityMissing(
                    toolName = meta.name,
                    reason = "'$tool' requires Termux, which is not installed. Install Termux from F-Droid to use this tool.",
                    missingCapability = ToolEvent.MissingCapability.TERMUX,
                )
            )
            send(ToolEvent.Done)
            return@channelFlow
        }

        try {
            val appSettings = runCatching { settingsRepository.getAppSettings().first() }.getOrNull()
            val timeoutMs = resolveTimeoutSeconds(
                toolName = tool.metadata.name,
                maxTimeoutSeconds = appSettings?.maxToolTimeoutSeconds,
            ) * 1_000L
            withTimeout(timeoutMs) {
                tool.shellExecutor = shellExecutor
                tool.execute(params) { event -> send(event) }
            }
        } catch (e: Throwable) {
            sendError(tool.metadata.name, e)
        }
    }
        .flowOn(Dispatchers.IO)

    // ------------------------------------------------------------------
    // Per-tool timeouts (seconds) — mirrors Python TOOL_TIMEOUTS
    // ------------------------------------------------------------------
    companion object {
        private const val DEFAULT_TIMEOUT_S = 60L

        val TOOL_TIMEOUTS: Map<String, Long> = mapOf(
            // Fast lookups
            "whois_lookup"          to 15,
            "ip_geolocation"        to 15,
            "ssl_inspector"         to 15,
            "email_header_analyzer" to 10,
            "encoder_tool"          to 5,
            "password_generator"    to 5,
            "reverse_shell_gen"     to 5,
            "payload_generator"     to 5,
            // Medium
            "dns_recon"             to 20,
            "cve_lookup"            to 20,
            "http_inspector"        to 20,
            "wifi_scanner"          to 30,
            "traceroute"            to 30,
            "xss_scanner"           to 60,
            "netbios_scanner"       to 60,
            // OSINT / long-running
            "linpeas"               to 120,
            "winpeas"               to 120,
            "bloodhound"            to 180,
            "nuclei"                to 180,
            "shodan_host"           to 30,
            "shodan_search"         to 30,
            "theharvester"          to 60,
            "hibp_check"            to 20,
            "hunter_verify"         to 20,
            // Long-running
            "lan_scanner"           to 90,
            "sqli_tester"           to 90,
            "subdomain_enum"        to 120,
            "packet_sniffer"        to 120,
            "arp_spoofer"           to 120,
            "port_scanner"          to 180,
            "network_scanner"       to 180,
            "directory_fuzzer"      to 180,
            "hash_cracker"          to 300,
            "brute_force"           to 600,
        )

        fun resolveTimeoutSeconds(toolName: String, maxTimeoutSeconds: Int?): Long {
            val configured = TOOL_TIMEOUTS[toolName] ?: DEFAULT_TIMEOUT_S
            val userMax = maxTimeoutSeconds?.takeIf { it > 0 }?.toLong() ?: return configured
            return minOf(configured, userMax)
        }
    }
}

private fun timeoutFor(toolName: String): Long =
    ToolExecutionEngine.TOOL_TIMEOUTS[toolName] ?: 60L

private suspend fun kotlinx.coroutines.channels.SendChannel<ToolEvent>.sendError(toolName: String, e: Throwable) {
    Timber.e(e, "Tool execution failed: %s", toolName)
    val message = when (e) {
        is TimeoutCancellationException ->
            "Tool '$toolName' timed out after ${timeoutFor(toolName)}s"
        is IllegalArgumentException -> e.message ?: "Invalid parameters"
        else -> e.message ?: "Unknown error"
    }
    send(ToolEvent.Error(message, e))
    send(ToolEvent.Done)
}
