package com.optix.android.tools.network

import com.optix.android.domain.model.HostResult
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.model.ParamType
import com.optix.android.tools.registry.BaseTool
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.isActive
import kotlinx.coroutines.withContext
import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withPermit
import java.net.InetAddress
import java.net.InetSocketAddress
import java.net.Socket

class NetworkScannerTool : BaseTool() {

    override val metadata = ToolMetadata(
        name = "network_scanner",
        displayName = "Network Scanner",
        description = "Ping-sweep a CIDR range to discover live hosts and open ports",
        category = ToolCategory.NETWORK_SCANNING,
        parameters = listOf(
            ToolParam("target", ParamType.STRING, "CIDR range or single IP (e.g. 192.168.1.0/24)"),
            ToolParam("ports", ParamType.STRING, "Comma-separated ports to probe", required = false, defaultValue = "22,80,443"),
            ToolParam("timeout_ms", ParamType.INT, "Socket timeout per host (ms)", required = false, defaultValue = "500"),
            ToolParam("threads", ParamType.INT, "Concurrent scan threads", required = false, defaultValue = "50"),
        ),
    )

    override suspend fun execute(params: Map<String, Any>, emit: suspend (ToolEvent) -> Unit) {
        val target = paramRequired(params, "target")
        val ports = param(params, "ports")?.split(",")?.mapNotNull { it.trim().toIntOrNull() } ?: listOf(80)
        val timeoutMs = paramInt(params, "timeout_ms", 500)

        emit(ToolEvent.Log("Starting network scan: $target"))

        val hosts = expandCidr(target)
        emit(ToolEvent.Log("Scanning ${hosts.size} hosts..."))

        val semaphore = Semaphore(50)
        hosts.forEachIndexed { idx, ip ->
            if (!kotlinx.coroutines.currentCoroutineContext().isActive) return
            withContext(Dispatchers.IO) {
                semaphore.withPermit {
                    val reachable = pingCmd(ip, timeoutMs)
                    val openPorts = ports.filter { port ->
                        runCatching {
                            Socket().use { s -> s.connect(InetSocketAddress(ip, port), timeoutMs); true }
                        }.getOrDefault(false)
                    }

                    if (reachable || openPorts.isNotEmpty()) {
                        val result = HostResult(ip = ip, status = "UP", openPorts = openPorts)
                        emit(ToolEvent.Result(result))
                        emit(ToolEvent.Log("  $ip UP  ports=${openPorts}"))
                    }
                }
            }
            emit(ToolEvent.Progress(idx + 1, hosts.size))
        }

        emit(ToolEvent.Log("Scan complete."))
        emit(ToolEvent.ChainSuggestion("nmap_termux", mapOf("target" to target)))
        emit(ToolEvent.Done)
    }

    private fun expandCidr(cidr: String): List<String> {
        if (!cidr.contains("/")) return listOf(cidr)
        val (base, prefixStr) = cidr.split("/")
        val prefix = prefixStr.toIntOrNull() ?: return listOf(base)
        val parts = base.split(".").map { it.toInt() }
        if (parts.size != 4) return listOf(base)
        val baseInt = (parts[0] shl 24) or (parts[1] shl 16) or (parts[2] shl 8) or parts[3]
        val mask = if (prefix == 0) 0 else (-1 shl (32 - prefix))
        val network = baseInt and mask
        val count = (1 shl (32 - prefix)).coerceAtMost(256) // cap at /24 for mobile
        return (1 until count - 1).map { i ->
            val addr = network + i
            "${(addr shr 24) and 0xFF}.${(addr shr 16) and 0xFF}.${(addr shr 8) and 0xFF}.${addr and 0xFF}"
        }
    }
}
