package infra

import org.json.JSONObject
import org.json.JSONArray
import java.net.HttpURLConnection
import java.net.URL
import java.net.URLEncoder

data class POI(
    val id: Long,
    val lat: Double,
    val lon: Double,
    val name: String,
    val category: String,
    val tags: Map<String, String>
)

private val CATEGORY_TAG_MAP: Map<String, String> = mapOf(
    "restaurant" to "amenity=restaurant",
    "cafe"       to "amenity=cafe",
    "bar"        to "amenity=bar",
    "museum"     to "tourism=museum",
    "monument"   to "historic=monument",
    "park"       to "leisure=park",
    "library"    to "amenity=library",
    "theatre"    to "amenity=theatre",
    "cinema"     to "amenity=cinema",
    "gallery"    to "tourism=gallery",
    "historic"   to "historic",
    "tourism"    to "tourism",
    "shop"       to "shop",
    "hotel"      to "tourism=hotel",
    "pharmacy"   to "amenity=pharmacy",
    "hospital"   to "amenity=hospital"
)

/** Builds the Overpass QL node clause for a single category tag expression. */
private fun nodeClause(tagExpr: String, radiusM: Int, lat: Double, lon: Double): String {
    return if (tagExpr.contains('=')) {
        val (key, value) = tagExpr.split('=', limit = 2)
        """node["$key"="$value"](around:$radiusM,$lat,$lon);"""
    } else {
        // Wildcard: match any value for the key
        """node["$tagExpr"](around:$radiusM,$lat,$lon);"""
    }
}

/**
 * OverpassNearbyPois queries the Overpass API (OpenStreetMap) to retrieve POIs
 * near the given coordinates within the specified radius.
 *
 * Uses only the Android SDK (java.net.HttpURLConnection + org.json) — no external
 * dependencies. Throws RuntimeException on HTTP or parse errors.
 *
 * @param lat       Latitude of the center point (WGS84)
 * @param lon       Longitude of the center point (WGS84)
 * @param radiusM   Search radius in metres (default: 500)
 * @param categories Subset of supported categories to filter by, or null for all
 * @return List of POIs found, may be empty
 */
fun overpassNearbyPois(
    lat: Double,
    lon: Double,
    radiusM: Int = 500,
    categories: List<String>? = null
): List<POI> {
    val resolved: Map<String, String> = if (categories == null) {
        CATEGORY_TAG_MAP
    } else {
        CATEGORY_TAG_MAP.filterKeys { it in categories }
    }

    if (resolved.isEmpty()) {
        throw RuntimeException("No valid categories resolved from: $categories")
    }

    val nodeClauses = resolved.values.joinToString("\n  ") { tagExpr ->
        nodeClause(tagExpr, radiusM, lat, lon)
    }
    val query = "[timeout:10][out:json];\n(\n  $nodeClauses\n);\nout body;"

    val endpoint = "https://overpass-api.de/api/interpreter"
    val encodedData = "data=" + URLEncoder.encode(query, "UTF-8")

    val conn = (URL(endpoint).openConnection() as HttpURLConnection).apply {
        requestMethod = "POST"
        connectTimeout = 10_000
        readTimeout = 10_000
        doOutput = true
        setRequestProperty("Content-Type", "application/x-www-form-urlencoded")
        setRequestProperty("Accept", "application/json")
    }

    try {
        conn.outputStream.use { it.write(encodedData.toByteArray(Charsets.UTF_8)) }

        val code = conn.responseCode
        if (code != 200) {
            val err = conn.errorStream?.bufferedReader()?.readText() ?: ""
            throw RuntimeException("Overpass API returned HTTP $code: $err")
        }

        val body = conn.inputStream.bufferedReader().readText()
        val root = JSONObject(body)
        val elements: JSONArray = root.optJSONArray("elements")
            ?: return emptyList()

        val tagExprByCategory: Map<String, String> = resolved.entries
            .associate { (cat, tagExpr) -> tagExpr to cat }

        val pois = mutableListOf<POI>()
        for (i in 0 until elements.length()) {
            val el = elements.getJSONObject(i)
            if (el.optString("type") != "node") continue

            val id  = el.getLong("id")
            val eLat = el.getDouble("lat")
            val eLon = el.getDouble("lon")
            val tagsObj: JSONObject = el.optJSONObject("tags") ?: JSONObject()

            // Build tags map
            val tagsMap = mutableMapOf<String, String>()
            for (key in tagsObj.keys()) {
                tagsMap[key] = tagsObj.getString(key)
            }

            // Determine category from the first matching tag expression
            val category = tagExprByCategory.entries.firstOrNull { (tagExpr, _) ->
                if (tagExpr.contains('=')) {
                    val (k, v) = tagExpr.split('=', limit = 2)
                    tagsMap[k] == v
                } else {
                    tagsMap.containsKey(tagExpr)
                }
            }?.value ?: "unknown"

            val name = tagsMap["name"] ?: category.takeIf { it != "unknown" } ?: "Unknown"

            pois.add(POI(id = id, lat = eLat, lon = eLon, name = name, category = category, tags = tagsMap))
        }

        return pois
    } finally {
        conn.disconnect()
    }
}
