package kr.lunaticbum.back.lun.services

import com.google.gson.Gson
import com.google.maps.GeoApiContext
import com.google.maps.PlacesApi
import com.google.maps.model.LatLng
import com.google.maps.model.PlaceType
import com.google.maps.model.PlacesSearchResult
import com.google.maps.model.RankBy
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.reactor.awaitSingleOrNull
import kr.lunaticbum.back.lun.configs.core.GlobalEnvironment
import kr.lunaticbum.back.lun.model.*
// [중요] 기존 Lama 대신 새로 만든 LamaService를 import 합니다.
// 모델 이름 충돌 방지를 위해 명시적 import
import kr.lunaticbum.back.lun.utils.LogService
import org.springframework.http.MediaType
import org.springframework.stereotype.Service
import org.springframework.web.reactive.function.BodyInserters
import org.springframework.web.reactive.function.client.WebClient
import java.math.BigDecimal
import java.math.RoundingMode
import java.text.SimpleDateFormat
import java.time.Duration
import java.util.*
import java.util.prefs.Preferences

@Service
class TelegramBotService(
    private val globalEvv: GlobalEnvironment,
    private val logService: LogService,
    private val locationLogService: LocationLogService,
    private val lamaService: LamaService // [수정] Lama -> LamaService로 변경
) {
    private val telegramApiBaseUrl = "https://api.telegram.org"

    fun processWebhookUpdate(update: Result?) {
        update?.message?.let { msg ->
            val loc = msg.location

            // 1. 위치 정보가 있는 경우
            if (loc != null && loc.latitude != 0.0) {
                handleLocationMessage(msg)
            }
            // 2. 명령어 처리
            else if (msg.text?.startsWith("/") == true) {
                // Command logic
            }
            // 3. "어디" 질문 처리
            else if (msg.text?.contains("어디") == true) {
                msg.from?.id?.let { sendCurrentLocation(it.toString()) }
            }
            // 4. AI 채팅
            else {
                handleAiChat(msg)
            }
        }
    }

    private fun handleLocationMessage(msg: TlgMessage) {
        CoroutineScope(Dispatchers.IO).launch {
            try {
                val userId = msg.from?.id?.toString() ?: return@launch

                val pref = Preferences.userNodeForPackage(TelegramBotService::class.java)
                var prefKey = pref.get("GAPI_KEY_${userId}", "")
                if (prefKey.length < 4) {
                    prefKey = globalEvv.gapiKey
                }

                if (!prefKey.isNullOrBlank()) {
                    val loc = msg.location!!
                    val lat = loc.latitude?.let { BigDecimal(it) }?.setScale(6, RoundingMode.HALF_UP)
                    val long = loc.longitude?.let { BigDecimal(it) }?.setScale(6, RoundingMode.HALF_UP)

                    lat?.let { long?.let { it1 -> fetchAndSendWeather(userId, it, it1) } }
                    lat?.let { long?.let { it1 -> fetchAndSendPlaces(userId, it.toDouble(), it1.toDouble(), prefKey) } }
                } else {
                    sendTelegramMessage(
                        userId,
                        "서비스 키를 등록해주세요.\n/setGaipKeys {key}"
                    )
                }
            } catch (e: Exception) {
                e.printStackTrace()
                logService.log("Location handling error: ${e.message}")
            }
        }
    }

    private fun fetchAndSendWeather(chatId: String, lat: BigDecimal, long: BigDecimal) {
        WebClient.create().get()
            .uri("http://api.weatherapi.com/v1/current.json?key=${globalEvv.weatherApiKey}&q=${lat},${long}&aqi=no")
            .retrieve()
            .bodyToMono(CurrentWeather::class.java)
            .timeout(Duration.ofSeconds(30L))
            .block()?.let { weather ->
                CoroutineScope(Dispatchers.IO).launch {
                    val summary = weather.getSummaryInfo(lat.toString(), long.toString())
                    sendTelegramMessage(chatId, summary)
                }
            }
    }

    private fun fetchAndSendPlaces(chatId: String, lat: Double, long: Double, apiKey: String) {
        val context = GeoApiContext.Builder()
            .apiKey(apiKey.trim())
            .build()

        val types = arrayOf(PlaceType.RESTAURANT, PlaceType.CAFE, PlaceType.BAR, PlaceType.BAKERY)

        types.forEach { type ->
            try {
                PlacesApi.nearbySearchQuery(context, LatLng(lat, long))
                    .type(type)
                    .rankby(RankBy.DISTANCE)
                    .language("ko")
                    .await()?.let { response ->
                        response.results
                            .filter { it.rating > 4 && it.userRatingsTotal > 1 }
                            .sortedBy { it.userRatingsTotal }
                            .forEach { place ->
                                val messageText = "${type.name} :: " + place.summary(lat, long)
                                sendTelegramMessage(chatId, messageText)
                            }
                    }
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
    }

    private fun handleAiChat(msg: TlgMessage) {
        val chatId = msg.from?.id?.toString() ?: return
        val text = msg.text ?: return

        val req = BumlamaReq(text)
        CoroutineScope(Dispatchers.IO).launch {
            val feedbackUrl = "$telegramApiBaseUrl/${globalEvv.telegramBotKey}/sendMessage?chat_id=${globalEvv.telegramMyId}&text=blama 일시키겠=> '${req.reqMsg}'"
            logService.log("AI Req: $feedbackUrl")
            WebClient.create().get()
                .uri(feedbackUrl)
                .retrieve()
                .bodyToMono(String::class.java).block()
        }

        CoroutineScope(Dispatchers.IO).launch {
            val dateContext = SimpleDateFormat("yyyy-MM-dd").format(Date())
            val modifiedQuery = text.replace("오늘", "오늘($dateContext)")

            // [수정] 서비스 객체 이름 변경 (lama -> lamaService)
            lamaService.generateResponse(modifiedQuery, chatId)
        }
    }

    private fun sendCurrentLocation(targetChatId: String) {
        CoroutineScope(Dispatchers.IO).launch {
            // [변경] awaitSingleOrNull() 사용
            val loc = locationLogService.getLocationLog().awaitSingleOrNull()

            if (loc != null) {
                val message = "${loc.timeString}\n${loc.mAddressLines.first()}\nhttps://www.google.com/maps/search/?api=1&query=$${loc.mLatitude},${loc.mLongitude}"
                sendTelegramMessage(targetChatId, message)
            }
        }
    }

    fun sendTelegramMessage(chatId: String, text: String) {
        val fullUrl = "$telegramApiBaseUrl/${globalEvv.telegramBotKey}/sendMessage"
        val msgObj = TelegramSendMsg(chatId, text)

        try {
            WebClient.create(fullUrl)
                .post()
                .contentType(MediaType.APPLICATION_JSON)
                .body(BodyInserters.fromValue(Gson().toJson(msgObj)))
                .retrieve()
                .bodyToMono(String::class.java)
                .timeout(Duration.ofMinutes(20L))
                .block()
        } catch (e: Exception) {
            logService.log("Failed to send telegram message: ${e.message}")
        }
    }

    // --- Helper Extensions ---
    private fun PlacesSearchResult.summary(currentLat: Double, currentLng: Double): String {
        val dist = calculateDistance(currentLat, currentLng, geometry!!.location!!.lat, geometry!!.location!!.lng)
        return "${name}\n총 리뷰수: ${userRatingsTotal}\n평점 : ${rating}\n거리 : \n${dist}km\n링크:\n https://www.google.com/maps/search/?api=1&query=${geometry!!.location!!.lat}%2C${geometry!!.location!!.lng}&query_place_id=${placeId}"
    }

    private fun calculateDistance(lat1: Double, lon1: Double, lat2: Double, lon2: Double): Double {
        val theta = lon1 - lon2
        var dist = Math.sin(Math.toRadians(lat1)) * Math.sin(Math.toRadians(lat2)) + Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2)) * Math.cos(Math.toRadians(theta))
        dist = Math.acos(dist)
        dist = Math.toDegrees(dist)
        dist = dist * 60 * 1.1515
        return (dist * 1.609344)
    }
}