package com.optix.android.tools.registry

import com.optix.android.domain.model.ToolEvent
import com.optix.android.domain.model.ToolMetadata
import com.optix.android.tools.engine.ShellExecutor

/**
 * Base class for every Optix tool.
 *
 * Subclasses declare their [metadata] and implement [execute].
 * The execution engine wraps [execute] with timeout + cancellation;
 * tools themselves only need to emit events and respect coroutine
 * cancellation (check [kotlinx.coroutines.isActive] in tight loops).
 */
abstract class BaseTool {

    var shellExecutor: ShellExecutor? = null

    abstract val metadata: ToolMetadata

    /**
     * Coroutine-based execution. Runs on [kotlinx.coroutines.Dispatchers.IO].
     *
     * @param params  validated parameter map (keys match [ToolMetadata.parameters])
     * @param emit    suspend callback to push [ToolEvent]s upstream
     *
     * Implementations MUST emit [ToolEvent.Done] as the final event.
     * The engine also emits Done on timeout/error, so tools should
     * return after emitting Done rather than continuing.
     */
    abstract suspend fun execute(
        params: Map<String, Any>,
        emit: suspend (ToolEvent) -> Unit,
    )

    // ------------------------------------------------------------------
    // Convenience helpers for subclasses
    // ------------------------------------------------------------------

    protected fun param(params: Map<String, Any>, key: String): String? =
        params[key]?.toString()

    protected fun paramRequired(
        params: Map<String, Any>,
        key: String,
    ): String = params[key]?.toString()
        ?: throw IllegalArgumentException("Required parameter '$key' is missing")

    protected fun paramInt(params: Map<String, Any>, key: String, default: Int): Int =
        params[key]?.toString()?.toIntOrNull() ?: default

    protected fun paramBool(params: Map<String, Any>, key: String, default: Boolean): Boolean =
        params[key]?.toString()?.toBooleanStrictOrNull() ?: default

    protected suspend fun emitLog(
        emit: suspend (ToolEvent) -> Unit,
        message: String,
        level: com.optix.android.domain.model.LogLevel = com.optix.android.domain.model.LogLevel.INFO,
    ) {
        emit(com.optix.android.domain.model.ToolEvent.Log(message, level))
    }

    /**
     * Pre-execution validation. Returns a list of error messages (empty = valid).
     * Override to add tool-specific validation beyond required-field checks.
     * The engine calls this before [execute] so the UI can show inline errors.
     */
    open fun validate(params: Map<String, Any>): List<String> {
        val errors = mutableListOf<String>()
        for (param in metadata.parameters) {
            if (param.required) {
                val value = params[param.name]?.toString()
                if (value.isNullOrBlank()) {
                    errors.add("'${param.name}' is required")
                }
            }
        }
        return errors
    }

    protected fun pingCmd(ip: String, timeoutMs: Int): Boolean {
        val timeoutSec = maxOf(1, timeoutMs / 1000)
        return try {
            val process = ProcessBuilder("ping", "-c", "1", "-W", timeoutSec.toString(), ip).start()
            val exitCode = process.waitFor()
            exitCode == 0
        } catch (e: Exception) {
            false
        }
    }
}
