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 org.json.JSONObject

// Shared URL-encoding helper for this file.
private fun String.urlEncode(): String = java.net.URLEncoder.encode(this, "UTF-8")

/**
 * Hunter.io — discover the email pattern used by a domain and verify the
 * deliverability of a specific address. Requires a Hunter.io API key.
 *
 *   Domain pattern discovery: https://api.hunter.io/v2/domain-search?domain={domain}&api_key={key}
 *   Email verification:        https://api.hunter.io/v2/email-verifier?email={email}&api_key={key}
 *
 * Docs: https://hunter.io/api/v2
 */
class HunterIoTool(private val client: OkHttpClient) : BaseTool() {

    override val metadata = ToolMetadata(
        name = "hunter_verify",
        displayName = "Hunter.io",
        description = "Discover a domain's email pattern or verify a specific email address using Hunter.io. Requires a Hunter.io API key.",
        category = ToolCategory.OSINT,
        parameters = listOf(
            ToolParam("domain", ParamType.STRING, "Target domain (e.g. example.com)"),
            ToolParam("email", ParamType.STRING, "Email to verify (optional — if omitted, domain pattern is returned)", required = false),
            ToolParam("api_key", ParamType.STRING, "Hunter.io API key (add it in Settings)"),
        ),
        apiKeyRef = "hunter",
        chainSuggestions = listOf("hibp_check", "emailrep_check", "theharvester"),
    )

    override suspend fun execute(params: Map<String, Any>, emit: suspend (ToolEvent) -> Unit) {
        val domain = paramRequired(params, "domain")
        val email = param(params, "email")
        val apiKey = param(params, "api_key")
        if (apiKey.isNullOrBlank()) {
            emit(ToolEvent.Log("⚠️ Hunter.io API key is missing. Add it in Settings → API Keys (Hunter)."))
            emit(ToolEvent.Error("Hunter.io API key required. Open Settings and add your Hunter.io API key."))
            emit(ToolEvent.Done)
            return
        }

        val base = "https://api.hunter.io/v2"
        val (_, mode) = if (email.isNullOrBlank()) {
            base to "domain-search"
        } else {
            base to "email-verifier"
        }
        val fullUrl = when (mode) {
            "domain-search" ->
                "$base/domain-search?domain=${domain.urlEncode()}&api_key=${apiKey.urlEncode()}"
            else ->
                "$base/email-verifier?email=${email?.urlEncode() ?: ""}&api_key=${apiKey.urlEncode()}"
        }

        emit(ToolEvent.Log("[Hunter.io] $mode for ${if (mode == "domain-search") domain else email}"))

        withContext(Dispatchers.IO) {
            val request = Request.Builder().url(fullUrl).header("User-Agent", "OptixV4").build()
            runCatching {
                client.newCall(request).execute().use { response ->
                    val body = response.body?.string().orEmpty()
                    when (response.code) {
                        401, 403 -> {
                            emit(ToolEvent.Error("Hunter.io rejected the API key (${response.code}). Check it in Settings."))
                            return@use
                        }
                        429 -> {
                            emit(ToolEvent.Error("Hunter.io rate limit reached (429). Upgrade your plan or retry later."))
                            return@use
                        }
                    }
                    if (body.isBlank()) {
                        emit(ToolEvent.Error("Empty response from Hunter.io (HTTP ${response.code})."))
                        return@use
                    }
                    val json = JSONObject(body)
                    if (json.has("errors")) {
                        val err = json.optJSONArray("errors")?.optJSONObject(0)?.optString("details")
                            ?: json.optString("errors")
                        emit(ToolEvent.Error("Hunter.io error: $err"))
                        return@use
                    }

                    val data = json.optJSONObject("data")
                    if (data == null) {
                        emit(ToolEvent.Error("Hunter.io returned no data."))
                        return@use
                    }

                    if (mode == "domain-search") {
                        data.optString("pattern").takeIf { it.isNotBlank() }
                            ?.let { emit(ToolEvent.Result(OsintResult("Email Pattern", it, "Hunter.io"))) }
                        data.optString("organization").takeIf { it.isNotBlank() }
                            ?.let { emit(ToolEvent.Result(OsintResult("Organization", it, "Hunter.io"))) }

                        val emails = data.optJSONArray("emails")
                        val count = emails?.length() ?: 0
                        emit(ToolEvent.Result(OsintResult("Emails Found", count.toString(), "Hunter.io")))
                        val limit = minOf(count, 25)
                        for (i in 0 until limit) {
                            val e = emails?.optJSONObject(i) ?: continue
                            val value = e.optString("value", "?")
                            val type = e.optString("type", "")
                            val score = e.optJSONObject("confidence")?.optInt("ai", -1) ?: -1
                            val label = buildString {
                                append(value)
                                if (type.isNotBlank()) append(" ($type)")
                                if (score >= 0) append(" score=$score")
                            }
                            emit(ToolEvent.Result(OsintResult("Email #$i", label, "Hunter.io")))
                        }
                    } else {
                        val status = data.optString("status", "")
                        val result = data.optString("result", "")
                        val score = data.optInt("score", -1)
                        val disposable = data.optBoolean("disposable", false)
                        val webmail = data.optBoolean("webmail", false)
                        emit(ToolEvent.Result(OsintResult("Email", email ?: "unknown", "Hunter.io")))
                        emit(ToolEvent.Result(OsintResult("Status", status, "Hunter.io")))
                        emit(ToolEvent.Result(OsintResult("Result", result, "Hunter.io")))
                        emit(ToolEvent.Result(OsintResult("Score", if (score >= 0) score.toString() else "n/a", "Hunter.io")))
                        emit(ToolEvent.Result(OsintResult("Disposable", disposable.toString(), "Hunter.io")))
                        emit(ToolEvent.Result(OsintResult("Webmail", webmail.toString(), "Hunter.io")))
                    }
                    emit(ToolEvent.Log("[Hunter.io] $mode complete."))
                }
            }.onFailure { emit(ToolEvent.Error("Hunter.io request failed: ${it.message}")) }
        }

        emit(ToolEvent.ChainSuggestion("hibp_check", mapOf("emailOrDomain" to (email ?: domain))))
        emit(ToolEvent.Done)
    }
}
