package com.android.trisolarisserver.component.geo import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.ObjectMapper import org.slf4j.LoggerFactory import org.springframework.http.HttpEntity import org.springframework.http.HttpHeaders import org.springframework.http.HttpMethod import org.springframework.beans.factory.annotation.Value import org.springframework.stereotype.Component import org.springframework.web.client.RestTemplate import org.springframework.web.util.UriComponentsBuilder @Component class PostalPincodeClient( private val restTemplate: RestTemplate, private val objectMapper: ObjectMapper, @Value("\${pincode.postal.baseUrl:https://api.postalpincode.in}") private val baseUrl: String ) { private val logger = LoggerFactory.getLogger(PostalPincodeClient::class.java) fun resolve(pinCode: String): PincodeLookupResult { val first = fetch(baseUrl, pinCode) if (first.resolvedCityState != null) return first if (first.status == "ERROR" && baseUrl.startsWith("https://")) { val httpUrl = baseUrl.replaceFirst("https://", "http://") val second = fetch(httpUrl, pinCode) if (second.resolvedCityState != null) return second return second } return first } private fun fetch(base: String, pinCode: String): PincodeLookupResult { val url = UriComponentsBuilder.fromUriString(base) .path("/pincode/{pin}") .buildAndExpand(pinCode) .toUriString() val headers = HttpHeaders().apply { set("User-Agent", "Mozilla/5.0 (TrisolarisServer)") set("Accept", "application/json") set("Connection", "close") } val entity = HttpEntity(headers) var lastError: Exception? = null repeat(2) { try { val response = restTemplate.exchange(url, HttpMethod.GET, entity, String::class.java) val body = response.body ?: return PincodeLookupResult(null, null, "EMPTY_BODY", "postalpincode.in", requestUrl = url) val resolved = parseCityState(body) val status = if (resolved == null) "ZERO_RESULTS" else "OK" return PincodeLookupResult(resolved, body, status, "postalpincode.in", requestUrl = url) } catch (ex: Exception) { lastError = ex try { Thread.sleep(200) } catch (_: InterruptedException) { Thread.currentThread().interrupt() } } } val errorMessage = lastError?.message if (errorMessage != null) { logger.warn("Postalpincode lookup failed: {}", errorMessage) } return PincodeLookupResult(null, null, "ERROR", "postalpincode.in", errorMessage, url) } private fun parseCityState(body: String): String? { val root = objectMapper.readTree(body) if (!root.isArray || root.isEmpty) return null val first = root.first() val status = first.path("Status").asText(null) if (!status.equals("Success", true)) return null val offices = first.path("PostOffice") if (!offices.isArray || offices.isEmpty) return null val office = chooseOffice(offices) ?: return null val district = office.path("District").asText(null) val state = office.path("State").asText(null) if (district.isNullOrBlank() && state.isNullOrBlank()) return null return listOfNotNull(district?.ifBlank { null }, state?.ifBlank { null }).joinToString(", ") } private fun chooseOffice(offices: JsonNode): JsonNode? { val delivery = offices.firstOrNull { it.path("DeliveryStatus").asText("").equals("Delivery", true) } return delivery ?: offices.firstOrNull() } }