package com.optix.android.tools.network

import com.optix.android.domain.model.ParamType
import com.optix.android.domain.model.PortResult
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.tools.registry.BaseTool
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withPermit
import kotlinx.coroutines.withContext
import java.util.concurrent.atomic.AtomicInteger
import java.net.InetAddress
import java.net.InetSocketAddress
import java.net.Socket

class PortScannerTool : BaseTool() {

    override val metadata = ToolMetadata(
        name = "port_scanner",
        displayName = "Port Scanner",
        description = "Deep TCP port scan against a single target with service detection",
        category = ToolCategory.NETWORK_SCANNING,
        parameters = listOf(
            ToolParam("target", ParamType.STRING, "Target IP or hostname"),
            ToolParam("start_port", ParamType.INT, "Start port", required = false, defaultValue = "1"),
            ToolParam("end_port", ParamType.INT, "End port", required = false, defaultValue = "9999"),
            ToolParam("timeout_ms", ParamType.INT, "Socket timeout per port (ms)", required = false, defaultValue = "500"),
        ),
    )

    override suspend fun execute(params: Map<String, Any>, emit: suspend (ToolEvent) -> Unit) {
        val target = paramRequired(params, "target")
        val startPort = paramInt(params, "start_port", 1)
        val endPort = paramInt(params, "end_port", 9999)
        val timeoutMs = paramInt(params, "timeout_ms", 500)
        val total = endPort - startPort + 1

        emit(ToolEvent.Log("Scanning $target ports $startPort-$endPort"))

        val address = withContext(Dispatchers.IO) {
            runCatching { InetAddress.getByName(target) }.getOrNull()
        } ?: run { emit(ToolEvent.Error("Cannot resolve $target")); emit(ToolEvent.Done); return }

        val semaphore = Semaphore(32)
        val scanned = AtomicInteger(0)
        val openPorts = mutableListOf<Int>()

        coroutineScope {
            (startPort..endPort).map { port ->
                async(Dispatchers.IO) {
                    semaphore.withPermit {
                        val open = runCatching {
                            Socket().use { s -> s.connect(InetSocketAddress(address, port), timeoutMs); true }
                        }.getOrDefault(false)

                        val completed = scanned.incrementAndGet()
                        emit(ToolEvent.Progress(completed, total))

                        if (open) {
                            val service = COMMON_PORTS[port] ?: "unknown"
                            synchronized(openPorts) { openPorts.add(port) }
                            emit(ToolEvent.Result(PortResult(port, "tcp", "open", service)))
                            emit(ToolEvent.Log("  $port/tcp  open  $service"))
                        }
                    }
                }
            }.awaitAll()
        }

        if (openPorts.isNotEmpty()) {
            when {
                openPorts.any { it in listOf(22) } ->
                    emit(ToolEvent.ChainSuggestion("ssh_audit", mapOf("target" to target)))
                openPorts.any { it in listOf(80, 443, 8080, 8443) } ->
                    emit(ToolEvent.ChainSuggestion("http_inspector", mapOf("url" to "http://$target")))
                openPorts.any { it in listOf(445, 139) } ->
                    emit(ToolEvent.ChainSuggestion("netbios_scanner", mapOf("target" to target)))
                else ->
                    emit(ToolEvent.ChainSuggestion("nmap_termux", mapOf("target" to target)))
            }
        }

        emit(ToolEvent.Done)
    }

    companion object {
        val COMMON_PORTS = mapOf(
            21 to "ftp", 22 to "ssh", 23 to "telnet", 25 to "smtp",
            53 to "dns", 80 to "http", 110 to "pop3", 143 to "imap",
            443 to "https", 445 to "smb", 3306 to "mysql", 3389 to "rdp",
            5432 to "postgresql", 6379 to "redis", 8080 to "http-alt",
            8443 to "https-alt", 27017 to "mongodb",
        )
    }
}
