package com.optix.android.tools.network

import com.optix.android.domain.model.OsintResult
import com.optix.android.domain.model.ParamType
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.withContext
import org.snmp4j.CommunityTarget
import org.snmp4j.PDU
import org.snmp4j.Snmp
import org.snmp4j.TransportMapping
import org.snmp4j.mp.SnmpConstants
import org.snmp4j.smi.Address
import org.snmp4j.smi.GenericAddress
import org.snmp4j.smi.Null
import org.snmp4j.smi.OID
import org.snmp4j.smi.OctetString
import org.snmp4j.smi.VariableBinding
import org.snmp4j.transport.DefaultUdpTransportMapping

class NativeSnmpWalkTool : BaseTool() {
    override val metadata = ToolMetadata(
        name = "native_snmpwalk",
        displayName = "SNMPwalk (Native)",
        description = "Native SNMP enumeration — walks the MIB tree to extract device info, interfaces without Termux.",
        category = ToolCategory.NETWORK_SCANNING,
        requiresTermux = false,
        requiresRoot = false,
        parameters = listOf(
            ToolParam("target", ParamType.STRING, "Target IP"),
            ToolParam("community", ParamType.STRING, "SNMP community string", required = false, defaultValue = "public"),
            ToolParam("version", ParamType.ENUM, "SNMP version", required = false, defaultValue = "2c", enumValues = listOf("1", "2c")),
        ),
        chainSuggestions = listOf("port_scanner", "native_smb_scanner"),
    )

    override suspend fun execute(params: Map<String, Any>, emit: suspend (ToolEvent) -> Unit) {
        val target = paramRequired(params, "target")
        val community = param(params, "community") ?: "public"
        val versionStr = param(params, "version") ?: "2c"

        val snmpVersion = if (versionStr == "1") SnmpConstants.version1 else SnmpConstants.version2c

        emit(ToolEvent.Log("[SNMPwalk] Walking $target natively with community '$community' (v$versionStr)..."))

        withContext(Dispatchers.IO) {
            var transport: TransportMapping<*>? = null
            var snmp: Snmp? = null
            try {
                transport = DefaultUdpTransportMapping()
                snmp = Snmp(transport)
                transport.listen()

                val targetAddress: Address = GenericAddress.parse("udp:$target/161")
                val comTarget = CommunityTarget<Address>()
                comTarget.community = OctetString(community)
                comTarget.version = snmpVersion
                comTarget.address = targetAddress
                comTarget.retries = 2
                comTarget.timeout = 1500

                // Walk mib-2 system group (1.3.6.1.2.1) using manual GETNEXT
                val rootOid = OID("1.3.6.1.2.1")
                emit(ToolEvent.Log("[SNMPwalk] Querying mib-2 tree..."))

                var currentOid = rootOid
                var resultCount = 0
                val maxResults = 200 // Safety limit

                while (resultCount < maxResults) {
                    val pdu = PDU()
                    pdu.add(VariableBinding(currentOid))
                    pdu.type = PDU.GETNEXT

                    val event = snmp.send(pdu, comTarget)
                    val responsePdu = event?.response

                    if (responsePdu == null) {
                        emit(ToolEvent.Log("[SNMPwalk] Timeout — no response from $target."))
                        break
                    }

                    if (responsePdu.errorStatus != PDU.noError) {
                        emit(ToolEvent.Log("[SNMPwalk] Error: ${responsePdu.errorStatusText}"))
                        break
                    }

                    val vb = responsePdu.get(0) ?: break
                    val responseOid = vb.oid
                    val responseValue = vb.variable

                    // Stop if we've walked past the mib-2 subtree or hit end-of-MIB
                    if (!responseOid.startsWith(rootOid) || responseValue is Null) {
                        break
                    }

                    val oidStr = responseOid.toString()
                    val valueStr = responseValue.toString()
                    emit(ToolEvent.Result(OsintResult("SNMP Value", valueStr, oidStr)))
                    emit(ToolEvent.Log("  $oidStr = $valueStr"))

                    currentOid = responseOid
                    resultCount++
                }

                if (resultCount == 0) {
                    emit(ToolEvent.Log("[SNMPwalk] No results. Host may be down or community string may be incorrect."))
                } else {
                    emit(ToolEvent.Log("[SNMPwalk] Walk complete. $resultCount OIDs retrieved."))
                }
            } catch (e: Exception) {
                emit(ToolEvent.Error("SNMP Walk Failed: ${e.message}", e))
            } finally {
                try {
                    snmp?.close()
                    transport?.close()
                } catch (_: Exception) {
                    // Ignore close exceptions
                }
            }
        }
        emit(ToolEvent.ChainSuggestion("nmap_termux", mapOf("target" to target)))
        emit(ToolEvent.Done)
    }
}
