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.withContext
import okhttp3.OkHttpClient
import okhttp3.Request
import java.security.MessageDigest

/**
 * Have I Been Pwned — breach / pwn check using the privacy-preserving
 * k-anonymity model.
 *
 * The input (email or domain) is hashed with SHA-1; only the first 5 hex
 * characters of the hash are sent to the Pwned Passwords range API, which
 * returns every hash suffix that shares that prefix. The local suffix is then
 * matched client-side, so the full value is never transmitted.
 *
 *   https://api.pwnedpasswords.com/range/{hashPrefix}
 *
 * Docs: https://haveibeenpwned.com/API/v3#PwnedPasswords
 */
class HIBPTool(private val client: OkHttpClient) : BaseTool() {

    override val metadata = ToolMetadata(
        name = "hibp_check",
        displayName = "Have I Been Pwned",
        description = "Check an email or domain against breach data using the HIBP k-anonymity range API (SHA-1, only first 5 chars sent).",
        category = ToolCategory.OSINT,
        parameters = listOf(
            ToolParam("emailOrDomain", ParamType.STRING, "Email address or domain to check"),
            ToolParam("api_key", ParamType.STRING, "HIBP API key (optional, recommended to avoid rate limits)", required = false),
        ),
        apiKeyRef = "hibp",
        chainSuggestions = listOf("dehashed_search", "emailrep_check", "theharvester"),
    )

    override suspend fun execute(params: Map<String, Any>, emit: suspend (ToolEvent) -> Unit) {
        val value = paramRequired(params, "emailOrDomain")
        val apiKey = param(params, "api_key")

        emit(ToolEvent.Log("[HIBP] Computing SHA-1 of input and querying k-anonymity range API (only first 5 hash chars sent)..."))

        withContext(Dispatchers.IO) {
            runCatching {
                val hash = sha1(value).uppercase()
                val prefix = hash.take(5)
                val suffix = hash.drop(5)

                val requestBuilder = Request.Builder()
                    .url("https://api.pwnedpasswords.com/range/$prefix")
                    .header("User-Agent", "OptixV4")
                    .header("Add-Padding", "true")
                apiKey?.takeIf { it.isNotBlank() }?.let {
                    requestBuilder.header("hibp-api-key", it)
                }
                val request = requestBuilder.build()

                client.newCall(request).execute().use { response ->
                    if (response.code == 429) {
                        emit(ToolEvent.Error("HIBP rate limit reached (429). Add an API key in Settings to raise the limit."))
                        return@use
                    }
                    val body = response.body?.string().orEmpty()
                    if (body.isBlank()) {
                        emit(ToolEvent.Error("Empty response from HIBP (HTTP ${response.code})."))
                        return@use
                    }

                    var breachCount = 0
                    for (line in body.lineSequence()) {
                        val parts = line.split(":", limit = 2)
                        if (parts.size == 2 && parts[0].equals(suffix, true)) {
                            breachCount = parts[1].trim().toIntOrNull() ?: 0
                            break
                        }
                    }

                    emit(ToolEvent.Result(OsintResult("Value", value, "HIBP")))
                    emit(ToolEvent.Result(OsintResult("SHA-1 Prefix Sent", prefix, "HIBP")))
                    emit(ToolEvent.Result(OsintResult("Pwn Count", breachCount.toString(), "HIBP")))
                    emit(ToolEvent.Result(OsintResult("Status", if (breachCount > 0) "COMPROMISED" else "CLEAN", "HIBP")))

                    emit(ToolEvent.Log(
                        if (breachCount > 0) "[HIBP] '$value' appears in breaches $breachCount time(s)."
                        else "[HIBP] No breach matches for '$value'."
                    ))
                }
            }.onFailure { emit(ToolEvent.Error("HIBP request failed: ${it.message}")) }
        }

        emit(ToolEvent.ChainSuggestion("dehashed_search", mapOf("query" to value, "queryType" to "email")))
        emit(ToolEvent.Done)
    }

    private fun sha1(input: String): String {
        val bytes = MessageDigest.getInstance("SHA-1").digest(input.toByteArray(Charsets.UTF_8))
        return bytes.joinToString("") { "%02x".format(it) }
    }
}
