package com.optix.android.tools.exploit

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

/**
 * CrackMapExec post-exploitation automation wrapper.
 *
 * Builds and runs:  crackmapexec {protocol} {target} -u {user} -p {pass} {action_flags}
 * through Termux as root. Successful findings are those tagged with "[+]".
 */
data class CmeResult(
    val target: String,
    val protocol: String,
    val action: String,
    val successCount: Int,
    val findings: List<String>,
)

class CMETool : BaseTool() {

    private val requiresTermux: Boolean = true

    override val metadata = ToolMetadata(
        name = "cme_termux",
        displayName = "CrackMapExec",
        description = "Post-exploitation automation across SMB/WinRM/SSH/MSSQL — spray, enum, pass-the-hash",
        category = ToolCategory.LATERAL_MOVEMENT,
        requiresRoot = true,
        parameters = listOf(
            ToolParam("target", ParamType.STRING, "Target host or CIDR (e.g. 10.0.0.0/24)"),
            ToolParam(
                "protocol", ParamType.ENUM, "Protocol / module",
                defaultValue = "smb",
                enumValues = listOf("smb", "winrm", "ssh", "mssql"),
            ),
            ToolParam("username", ParamType.STRING, "Username or user file path", required = false, defaultValue = ""),
            ToolParam("password", ParamType.STRING, "Password or pass file path", required = false, defaultValue = ""),
            ToolParam(
                "action", ParamType.ENUM, "Action to perform",
                defaultValue = "enum",
                enumValues = listOf("spray", "enum", "pass_the_hash"),
            ),
        ),
        chainSuggestions = listOf("winpeas_termux", "rubeus_termux", "bloodhound_termux"),
    )

    override fun validate(params: Map<String, Any>): List<String> {
        val errors = super.validate(params).toMutableList()
        val protocol = param(params, "protocol") ?: "smb"
        if (protocol !in listOf("smb", "winrm", "ssh", "mssql")) errors.add("'protocol' must be smb/winrm/ssh/mssql")
        val action = param(params, "action") ?: "enum"
        if (action !in listOf("spray", "enum", "pass_the_hash")) errors.add("'action' must be spray/enum/pass_the_hash")
        // Credentials are required for anything other than a pure enum of null sessions.
        if (action != "enum") {
            val user = param(params, "username") ?: ""
            val pass = param(params, "password") ?: ""
            if (action == "pass_the_hash") {
                if (pass.isBlank()) errors.add("'password' (NTLM hash) is required for pass_the_hash")
            } else {
                if (user.isBlank()) errors.add("'username' is required for $action")
                if (pass.isBlank()) errors.add("'password' is required for $action")
            }
        }
        return errors
    }

    override suspend fun execute(params: Map<String, Any>, emit: suspend (ToolEvent) -> Unit) {
        val target = paramRequired(params, "target")
        val protocol = param(params, "protocol") ?: "smb"
        val username = param(params, "username") ?: ""
        val password = param(params, "password") ?: ""
        val action = param(params, "action") ?: "enum"

        emit(ToolEvent.Log("Running CrackMapExec ($action) against $target over $protocol (Termux + root required)."))

        val args = buildList {
            add(target)
            val uFlag = if (username.startsWith("/")) "-U" else "-u"
            val pFlag = if (password.startsWith("/")) "-P" else "-p"
            if (username.isNotBlank()) { add(uFlag); add(username) }
            if (password.isNotBlank()) { add(pFlag); add(password) }
            when (action) {
                "enum" -> add("--generate-hosts-file")
                "spray" -> add("--continue-on-success")
                "pass_the_hash" -> { add("--pth"); add("-p"); add(password) } // password holds the NTLM hash
            }
        }

        emit(ToolEvent.Log("▶ crackmapexec $protocol ${args.joinToString(" ")}"))

        val findings = mutableListOf<String>()
        withContext(Dispatchers.IO) {
            // Re-implement a minimal reader here so we can capture "[+]" successes directly.
            // We mirror ShellExecutor.runSu but additionally parse CME's positive markers.
            try {
                val cmd = buildString {
                    append("/data/data/com.termux/files/usr/bin/crackmapexec $protocol ")
                    append(args.joinToString(" ") { it.shellEscapeTermux() })
                }
                val process = ProcessBuilder("su", "-c", cmd).redirectErrorStream(true).start()
                val reader = process.inputStream.bufferedReader()
                while (true) {
                    if (!isActive) { process.destroy(); break }
                    val line = reader.readLine() ?: break
                    emit(ToolEvent.Log(line))
                    if (line.contains("[+]")) findings.add(line.trim())
                }
                process.waitFor()
            } catch (e: Exception) {
                emit(ToolEvent.Error("CrackMapExec failed: ${e.message}"))
            }
        }

        emit(ToolEvent.Log("CrackMapExec finished — ${findings.size} successful finding(s)."))
        emit(ToolEvent.Result(CmeResult(target, protocol, action, findings.size, findings)))
        emit(ToolEvent.ChainSuggestion("winpeas_termux", mapOf("target" to target)))
        emit(ToolEvent.Done)
    }

    private fun String.shellEscapeTermux(): String = "'" + this.replace("'", "'\\''") + "'"
}
