package com.optix.android.tools.osint

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.currentCoroutineContext
import kotlinx.coroutines.isActive
import kotlinx.coroutines.withContext

/**
 * theHarvester — open-source intelligence gathering tool that enumerates
 * emails, hosts/subdomains, and other artifacts about a domain from public
 * sources (search engines, DNS, PGP, etc). Runs via Termux.
 *
 *   theharvester -d <domain> -b <source>
 *
 * Sources: all | google | bing | baidu | dnsdumpster
 */
class HarvesterTool : BaseTool() {

    override val metadata = ToolMetadata(
        name = "theharvester",
        displayName = "theHarvester",
        description = "Enumerate emails, subdomains and hosts for a domain using theHarvester via Termux. Requires Termux.",
        category = ToolCategory.OSINT,
        requiresTermux = true,
        requiresRoot = false,
        requiredPackages = listOf("theharvester"),
        parameters = listOf(
            ToolParam("domain", ParamType.STRING, "Target domain (e.g. example.com)"),
            ToolParam(
                "source", ParamType.ENUM, "Data source to query", required = false, defaultValue = "all",
                enumValues = listOf("all", "google", "bing", "baidu", "dnsdumpster"),
            ),
        ),
        chainSuggestions = listOf("hibp_check", "subdomain_enum", "dns_recon"),
    )

    override suspend fun execute(params: Map<String, Any>, emit: suspend (ToolEvent) -> Unit) {
        val domain = paramRequired(params, "domain")
        val source = param(params, "source") ?: "all"

        emit(ToolEvent.Log("[theHarvester] Enumerating '$domain' using source '$source'..."))
        emit(ToolEvent.Log("[theHarvester] This may take a while depending on the source."))

        val output = runTermuxCaptured(
            toolName = "theharvester",
            args = listOf("-d", domain, "-b", source, "-f", "/dev/null"),
            aptPackage = "theharvester",
            emit = emit,
        )

        if (output.isBlank()) {
            emit(ToolEvent.Log("[theHarvester] No output captured."))
            emit(ToolEvent.Done)
            return
        }

        val emails = EMAIL_REGEX.findAll(output).map { it.value.lowercase() }.toSet().toList()
        val subdomains = SUBDOMAIN_REGEX.findAll(output)
            .map { it.value.lowercase() }
            .filter { it.endsWith(domain) }
            .toSet().toList()

        emit(ToolEvent.Result(OsintResult("Emails Found", emails.size.toString(), "theHarvester")))
        emails.take(50).forEachIndexed { i, email ->
            emit(ToolEvent.Result(OsintResult("Email #$i", email, "theHarvester")))
        }

        emit(ToolEvent.Result(OsintResult("Subdomains / Hosts Found", subdomains.size.toString(), "theHarvester")))
        subdomains.take(100).forEachIndexed { i, host ->
            emit(ToolEvent.Result(OsintResult("Host #$i", host, "theHarvester")))
        }

        emit(ToolEvent.Log("[theHarvester] Done — ${emails.size} email(s), ${subdomains.size} host(s)."))
        emit(ToolEvent.ChainSuggestion("hibp_check", mapOf("emailOrDomain" to domain)))
        emit(ToolEvent.Done)
    }

    /**
     * Runs a Termux binary via `su -c`, streaming each line as a [ToolEvent.Log]
     * and returning the full captured output for parsing. Surfaces a missing
     * binary via [ToolEvent.InstallRequired].
     */
    private suspend fun runTermuxCaptured(
        toolName: String,
        args: List<String>,
        aptPackage: String,
        emit: suspend (ToolEvent) -> Unit,
    ): String = withContext(Dispatchers.IO) {
        val se = shellExecutor
        if (se == null) {
            emit(ToolEvent.Error("ShellExecutor unavailable — cannot run $toolName."))
            return@withContext ""
        }
        val escaped = se.run { args.joinToString(" ") { it.shellEscape() } }
        val toolPath = "/data/data/com.termux/files/usr/bin/$toolName"
        val shellCommand = "$toolPath $escaped"

        try {
            val process = ProcessBuilder("su", "-c", shellCommand)
                .redirectErrorStream(true)
                .start()
            val builder = StringBuilder()
            val reader = process.inputStream.bufferedReader()
            while (true) {
                if (!currentCoroutineContext().isActive) {
                    process.destroy()
                    break
                }
                val line = reader.readLine() ?: break
                if (line.isNotBlank()) {
                    builder.appendLine(line)
                    if (line.contains("command not found", true) || line.contains("No such file", true)) {
                        emit(ToolEvent.InstallRequired(toolName, aptPackage))
                    } else {
                        emit(ToolEvent.Log(line))
                    }
                }
            }
            process.waitFor()
            builder.toString()
        } catch (e: Exception) {
            emit(ToolEvent.Error("theHarvester execution failed: ${e.message}"))
            ""
        }
    }

    companion object {
        private val EMAIL_REGEX = Regex("""[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}""")
        private val SUBDOMAIN_REGEX = Regex("""[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?)+""")
    }
}
