package com.optix.android.tools.utility

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 java.security.MessageDigest
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec

/** Subnet Calculator — CIDR math, no network needed. */
class SubnetCalculatorTool : BaseTool() {
    override val metadata = ToolMetadata(
        name = "subnet_calculator",
        displayName = "Subnet Calculator",
        description = "CIDR subnet calculator: network address, broadcast, host range, number of hosts",
        category = ToolCategory.PAYLOADS_UTILITIES,
        requiresNetwork = false,
        parameters = listOf(
            ToolParam("cidr", ParamType.STRING, "IP/prefix notation (e.g. 192.168.1.0/24 or 10.0.0.1/8)"),
        ),
    )

    override suspend fun execute(params: Map<String, Any>, emit: suspend (ToolEvent) -> Unit) {
        val cidr = paramRequired(params, "cidr").trim()
        try {
            val (ipStr, prefixStr) = cidr.split("/")
            val prefix = prefixStr.toInt()
            if (prefix < 0 || prefix > 32) throw IllegalArgumentException("Prefix must be 0-32")

            val ipParts = ipStr.split(".").map { it.toInt() }
            if (ipParts.size != 4 || ipParts.any { it < 0 || it > 255 }) throw IllegalArgumentException("Invalid IP")
            val ipInt = (ipParts[0] shl 24) or (ipParts[1] shl 16) or (ipParts[2] shl 8) or ipParts[3]

            val mask = if (prefix == 0) 0 else (0xFFFFFFFF.toInt() shl (32 - prefix))
            val network = ipInt and mask
            val broadcast = network or mask.inv()
            val firstHost = network + 1
            val lastHost = broadcast - 1
            val totalHosts = if (prefix >= 31) (1 shl (32 - prefix)) else (1 shl (32 - prefix)) - 2

            fun intToIp(i: Int) = "${(i ushr 24) and 0xFF}.${(i ushr 16) and 0xFF}.${(i ushr 8) and 0xFF}.${i and 0xFF}"

            emit(ToolEvent.Log("── Subnet Info for $cidr ──────────────"))
            val info = mapOf(
                "Network Address" to intToIp(network),
                "Subnet Mask" to intToIp(mask),
                "Broadcast Address" to intToIp(broadcast),
                "First Usable Host" to intToIp(firstHost),
                "Last Usable Host" to intToIp(lastHost),
                "Total Usable Hosts" to totalHosts.toString(),
                "CIDR Notation" to "$cidr",
                "Binary Mask" to mask.toUInt().toString(2).padStart(32, '0').chunked(8).joinToString("."),
            )
            info.forEach { (k, v) ->
                emit(ToolEvent.Log("  $k: $v"))
                emit(ToolEvent.Result(OsintResult(k, v, "SubnetCalc")))
            }

            // Wildcard mask (used in Cisco ACLs)
            val wildcard = mask.inv()
            emit(ToolEvent.Log("  Wildcard Mask: ${intToIp(wildcard)}"))
            emit(ToolEvent.Result(OsintResult("Wildcard Mask", intToIp(wildcard), "SubnetCalc")))
        } catch (e: Exception) {
            emit(ToolEvent.Error("Subnet calc error: ${e.message} — try format like 192.168.1.0/24"))
        }
        emit(ToolEvent.ChainSuggestion("lan_scanner", mapOf("subnet" to cidr.substringBefore("/"))))
        emit(ToolEvent.Done)
    }
}

/** Hash Generator — generate hashes from text. */
class HashGeneratorTool : BaseTool() {
    override val metadata = ToolMetadata(
        name = "hash_generator",
        displayName = "Hash Generator",
        description = "Generate MD5, SHA-1, SHA-256, SHA-512, and HMAC hashes from any input",
        category = ToolCategory.PAYLOADS_UTILITIES,
        requiresNetwork = false,
        parameters = listOf(
            ToolParam("input", ParamType.STRING, "Text to hash"),
            ToolParam("algorithm", ParamType.ENUM, "Hash algorithm", required = false, defaultValue = "ALL",
                enumValues = listOf("ALL", "MD5", "SHA-1", "SHA-256", "SHA-512")),
            ToolParam("hmac_key", ParamType.STRING, "Optional HMAC key (leave empty for plain hash)", required = false, defaultValue = ""),
        ),
    )

    override suspend fun execute(params: Map<String, Any>, emit: suspend (ToolEvent) -> Unit) {
        val input = paramRequired(params, "input")
        val alg = param(params, "algorithm") ?: "ALL"
        val hmacKey = param(params, "hmac_key") ?: ""

        emit(ToolEvent.Log("Hashing input (${input.length} chars)..."))
        emit(ToolEvent.Log(""))

        val algorithms = if (alg == "ALL") listOf("MD5", "SHA-1", "SHA-256", "SHA-512") else listOf(alg)

        algorithms.forEach { a ->
            try {
                val hash = if (hmacKey.isNotEmpty()) {
                    val mac = Mac.getInstance("Hmac${a.replace("-", "")}")
                    mac.init(SecretKeySpec(hmacKey.toByteArray(), "Hmac${a.replace("-", "")}"))
                    mac.doFinal(input.toByteArray()).joinToString("") { "%02x".format(it) }
                } else {
                    MessageDigest.getInstance(a).digest(input.toByteArray()).joinToString("") { "%02x".format(it) }
                }
                val label = if (hmacKey.isNotEmpty()) "HMAC-$a" else a
                emit(ToolEvent.Log("$label:"))
                emit(ToolEvent.Log("  $hash"))
                emit(ToolEvent.Result(OsintResult(label, hash, "HashGen")))
            } catch (e: Exception) {
                emit(ToolEvent.Log("$a: ${e.message}"))
            }
        }
        emit(ToolEvent.ChainSuggestion("hash_cracker", mapOf("hash" to input)))
        emit(ToolEvent.Done)
    }
}

/** Cipher Tool — classical and modern ciphers for CTF work. */
class CipherTool : BaseTool() {
    override val metadata = ToolMetadata(
        name = "cipher_tool",
        displayName = "Cipher Tool",
        description = "Encrypt/decrypt: Caesar, ROT13, Vigenère, XOR, Atbash — great for CTF challenges",
        category = ToolCategory.PAYLOADS_UTILITIES,
        requiresNetwork = false,
        parameters = listOf(
            ToolParam("text", ParamType.STRING, "Input text"),
            ToolParam("mode", ParamType.ENUM, "Cipher mode", defaultValue = "rot13",
                enumValues = listOf("rot13", "caesar_enc", "caesar_dec", "vigenere_enc", "vigenere_dec",
                    "xor", "atbash", "base85_enc", "base85_dec", "morse_enc")),
            ToolParam("key", ParamType.STRING, "Key (shift for Caesar, keyword for Vigenère, byte for XOR)", required = false, defaultValue = "13"),
        ),
    )

    override suspend fun execute(params: Map<String, Any>, emit: suspend (ToolEvent) -> Unit) {
        val text = paramRequired(params, "text")
        val mode = paramRequired(params, "mode")
        val key = param(params, "key") ?: "13"

        val result = when (mode) {
            "rot13" -> text.map { c ->
                when { c.isUpperCase() -> (((c - 'A') + 13) % 26 + 'A'.code).toChar()
                    c.isLowerCase() -> (((c - 'a') + 13) % 26 + 'a'.code).toChar()
                    else -> c }
            }.joinToString("")

            "caesar_enc", "caesar_dec" -> {
                val shift = (key.toIntOrNull() ?: 13) * (if (mode == "caesar_dec") -1 else 1)
                text.map { c ->
                    when { c.isUpperCase() -> (((c - 'A') + shift).mod(26) + 'A'.code).toChar()
                        c.isLowerCase() -> (((c - 'a') + shift).mod(26) + 'a'.code).toChar()
                        else -> c }
                }.joinToString("")
            }

            "atbash" -> text.map { c ->
                when { c.isUpperCase() -> ('Z' - (c - 'A'))
                    c.isLowerCase() -> ('z' - (c - 'a'))
                    else -> c }
            }.joinToString("")

            "vigenere_enc", "vigenere_dec" -> {
                val k = key.lowercase().filter { it.isLetter() }
                if (k.isEmpty()) "Key must contain letters"
                else {
                    var ki = 0
                    text.map { c ->
                        if (!c.isLetter()) c
                        else {
                            val shift = k[ki % k.length] - 'a'
                            ki++
                            val base = if (c.isUpperCase()) 'A' else 'a'
                            val s = if (mode == "vigenere_dec") 26 - shift else shift
                            ((c - base + s) % 26 + base.code).toChar()
                        }
                    }.joinToString("")
                }
            }

            "xor" -> {
                val xorByte = key.toIntOrNull()?.toByte() ?: key.firstOrNull()?.code?.toByte() ?: 0x41
                text.map { c -> (c.code xor xorByte.toInt()).toChar() }.joinToString("")
            }

            "morse_enc" -> {
                val morseMap = mapOf('A' to ".-", 'B' to "-...", 'C' to "-.-.", 'D' to "-..", 'E' to ".",
                    'F' to "..-.", 'G' to "--.", 'H' to "....", 'I' to "..", 'J' to ".---",
                    'K' to "-.-", 'L' to ".-..", 'M' to "--", 'N' to "-.", 'O' to "---",
                    'P' to ".--.", 'Q' to "--.-", 'R' to ".-.", 'S' to "...", 'T' to "-",
                    'U' to "..-", 'V' to "...-", 'W' to ".--", 'X' to "-..-", 'Y' to "-.--",
                    'Z' to "--..", '0' to "-----", '1' to ".----", '2' to "..---", '3' to "...--",
                    '4' to "....-", '5' to ".....", '6' to "-....", '7' to "--...", '8' to "---..",
                    '9' to "----.")
                text.uppercase().map { c -> morseMap[c] ?: if (c == ' ') "/" else "?" }.joinToString(" ")
            }

            else -> "Unknown mode"
        }

        emit(ToolEvent.Log("Mode: $mode"))
        emit(ToolEvent.Log("Input:  $text"))
        emit(ToolEvent.Log("Output: $result"))
        emit(ToolEvent.Result(OsintResult("Result", result, "Cipher")))
        emit(ToolEvent.ChainSuggestion("encoder_tool", mapOf("operation" to "encode", "format" to "base64", "input" to text)))
        emit(ToolEvent.Done)
    }
}

/** Regex Tester — live regex matching. */
class RegexTesterTool : BaseTool() {
    override val metadata = ToolMetadata(
        name = "regex_tester",
        displayName = "Regex Tester",
        description = "Test regular expressions against text — find matches, groups, and counts",
        category = ToolCategory.PAYLOADS_UTILITIES,
        requiresNetwork = false,
        parameters = listOf(
            ToolParam("pattern", ParamType.STRING, "Regex pattern (e.g. \\d{3}-\\d{4})"),
            ToolParam("text", ParamType.STRING, "Input text to test against"),
            ToolParam("flags", ParamType.ENUM, "Options", required = false, defaultValue = "none",
                enumValues = listOf("none", "ignore_case", "multiline", "dotall")),
        ),
    )

    override suspend fun execute(params: Map<String, Any>, emit: suspend (ToolEvent) -> Unit) {
        val pattern = paramRequired(params, "pattern")
        val text = paramRequired(params, "text")
        val flags = param(params, "flags") ?: "none"

        try {
            val options = mutableSetOf<RegexOption>()
            if (flags.contains("ignore_case")) options.add(RegexOption.IGNORE_CASE)
            if (flags.contains("multiline")) options.add(RegexOption.MULTILINE)
            if (flags.contains("dotall")) options.add(RegexOption.DOT_MATCHES_ALL)

            val regex = Regex(pattern, options)
            val matches = regex.findAll(text).toList()

            emit(ToolEvent.Log("Pattern: $pattern"))
            emit(ToolEvent.Log("Flags: $flags"))
            emit(ToolEvent.Log("Matches found: ${matches.size}"))
            emit(ToolEvent.Log(""))

            if (matches.isEmpty()) {
                emit(ToolEvent.Log("No matches found."))
            } else {
                matches.forEachIndexed { i, match ->
                    emit(ToolEvent.Log("Match ${i + 1}: '${match.value}' at position ${match.range}"))
                    if (match.groupValues.size > 1) {
                        match.groupValues.drop(1).forEachIndexed { gi, g ->
                            if (g.isNotEmpty()) emit(ToolEvent.Log("  Group ${gi + 1}: '$g'"))
                        }
                    }
                    emit(ToolEvent.Result(OsintResult("Match ${i + 1}", match.value, "regex@${match.range}")))
                }
            }

            // Test useful common patterns as suggestions
            emit(ToolEvent.Log(""))
            emit(ToolEvent.Log("Common patterns: \\d+ (numbers), [a-zA-Z]+ (words), \\b\\w+@\\w+\\.\\w+\\b (emails)"))
        } catch (e: Exception) {
            emit(ToolEvent.Error("Invalid regex: ${e.message}"))
        }
        emit(ToolEvent.ChainSuggestion("payload_generator", mapOf("type" to "xss")))
        emit(ToolEvent.Done)
    }
}
