package com.optix.android.tools.parser

import com.optix.android.domain.model.OpenPort
import com.optix.android.domain.model.ServiceInfo

/**
 * Pure, Android-free parser for nmap "normal" (-oN / default) text output.
 *
 * Turns lines like:
 *   Nmap scan report for router (192.168.1.1)
 *   22/tcp   open  ssh     OpenSSH 8.9p1 Ubuntu-3ubuntu0.6
 *   80/tcp   open  http    nginx 1.24.0
 *   443/tcp  filtered https
 * into structured [OpenPort] / [ServiceInfo] objects.
 *
 * Kept as a stateless object with pure functions so it can be unit-tested
 * without a device or coroutine machinery (item #7).
 */
object NmapParser {

    // 22/tcp open ssh OpenSSH 8.9p1
    private val PORT_LINE = Regex(
        """^(\d{1,5})/(tcp|udp)\s+(\w[\w|-]*)\s+(\S+)?\s*(.*)$""",
        RegexOption.IGNORE_CASE,
    )

    // "Nmap scan report for router (192.168.1.1)" or "... for 192.168.1.1"
    private val HOST_LINE = Regex(
        """Nmap scan report for\s+(?:(\S+)\s+\(([\d.]+)\)|([\d.\w:-]+))""",
        RegexOption.IGNORE_CASE,
    )

    data class NmapReport(
        val host: String,
        val openPorts: List<OpenPort>,
        val services: List<ServiceInfo>,
    )

    /** Parse a full nmap run for a single primary host. */
    fun parse(output: String, fallbackHost: String = "unknown"): NmapReport {
        var host = fallbackHost
        val ports = mutableListOf<OpenPort>()
        val services = mutableListOf<ServiceInfo>()

        output.lineSequence().forEach { rawLine ->
            val line = rawLine.trim()
            HOST_LINE.find(line)?.let { m ->
                host = m.groupValues[2].ifBlank { m.groupValues[3].ifBlank { m.groupValues[1] } }
                    .ifBlank { host }
                return@forEach
            }
            parsePortLine(line, host)?.let { port ->
                ports += port
                if (port.state.equals("open", true)) {
                    services += ServiceInfo(
                        host = host,
                        port = port.port,
                        service = port.service,
                        product = port.product,
                        version = port.version,
                    )
                }
            }
        }
        return NmapReport(host, ports, services)
    }

    /** Parse a single "PORT/proto state service product version" line. Null if not a port line. */
    fun parsePortLine(line: String, host: String): OpenPort? {
        val m = PORT_LINE.find(line.trim()) ?: return null
        val port = m.groupValues[1].toIntOrNull() ?: return null
        val proto = m.groupValues[2].lowercase()
        val state = m.groupValues[3].lowercase()
        val service = m.groupValues[4].ifBlank { "unknown" }
        val versionInfo = m.groupValues[5].trim()

        val product = versionInfo.substringBefore(' ', versionInfo).ifBlank { null }
        val version = versionInfo.substringAfter(' ', "").trim().ifBlank { null }

        return OpenPort(
            host = host,
            port = port,
            protocol = proto,
            state = state,
            service = service,
            product = product,
            version = version,
        )
    }
}
