package kr.lunaticbum.back.lun.services

import kr.lunaticbum.back.lun.model.LocationLog
import kr.lunaticbum.back.lun.model.LocationLogRepository
import kr.lunaticbum.back.lun.utils.LogService
import org.springframework.data.domain.Page
import org.springframework.data.domain.PageImpl
import org.springframework.data.domain.Pageable
import org.springframework.data.domain.Sort
import org.springframework.stereotype.Service
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
import kotlin.math.atan2
import kotlin.math.cos
import kotlin.math.sin
import kotlin.math.sqrt

@Service
class LocationLogService(
    private val logRepository: LocationLogRepository,
    private val logService: LogService
) {

    /**
     * [성능 개선] block() 제거하고 Mono<Page> 반환
     * 전체 카운트와 데이터를 병렬로 조회하여 합칩니다.
     */
    fun findAll(pageable: Pageable): Mono<Page<LocationLog>> {
        val dataMono = logRepository.findAll(pageable.getSort())
            .skip(pageable.offset)
            .take(pageable.pageSize.toLong())
            .collectList()

        val countMono = logRepository.count()

        return Mono.zip(dataMono, countMono).map { tuple ->
            PageImpl(tuple.t1, pageable, tuple.t2)
        }
    }

    /**
     * [성능 개선] block() 제거. 최근 100일간의 데이터를 거리 필터링하여 반환
     */
    fun find10(): Flux<LocationLog> {
        val sinceMills = System.currentTimeMillis() - ((24 * 60 * 60 * 1000L) * 100)
        val sort = Sort.by(Sort.Direction.DESC, "time")

        return filterByDistanceReactive(logRepository.findRecent(sinceMills, sort), 10.0)
    }

    /**
     * [성능 개선] 가장 최근 위치 하나 조회 (Mono 반환)
     */
    fun getLocationLog(): Mono<LocationLog> {
        return logRepository.findFirstByOrderByTimeDesc()
    }

    fun getLocationLogBy(userId: String): Mono<LocationLog> {
        return logRepository.findFirstByUserIdOrderByTimeDesc(userId)
    }

    /**
     * [성능 개선] 저장 로직 (subscribe 제거하고 Mono 반환)
     * 호출하는 쪽에서 구독해야 실제로 저장됩니다.
     */
    fun save(log: LocationLog): Mono<LocationLog> {
        logService.log("Saving location: ${log.mAddressLines.firstOrNull()}")
        return logRepository.save(log)
    }

    /**
     * Reactive Stream 거리 필터링 (기존 로직 유지하되 Flux 처리)
     */
    private fun filterByDistanceReactive(flux: Flux<LocationLog>, minDistanceMeter: Double): Flux<LocationLog> {
        return flux.buffer(2, 1) // 이전 요소와 현재 요소를 묶어서 처리
            .filter { pair ->
                if (pair.size < 2) true
                else haversine(pair[0].mLatitude, pair[0].mLongitude, pair[1].mLatitude, pair[1].mLongitude) >= minDistanceMeter
            }
            .map { pair ->
                val current = pair[0]
                if (pair.size >= 2) {
                    val distance = haversine(current.mLatitude, current.mLongitude, pair[1].mLatitude, pair[1].mLongitude)
                    current.bettween = String.format("%.2f m", distance)
                }
                current
            }
    }

    // Haversine 거리계산 (단위: m)
    private fun haversine(lat1: Double, lon1: Double, lat2: Double, lon2: Double): Double {
        val R = 6371000.0
        val dLat = Math.toRadians(lat2 - lat1)
        val dLon = Math.toRadians(lon2 - lon1)
        val a = sin(dLat / 2) * sin(dLat / 2) +
                cos(Math.toRadians(lat1)) * cos(Math.toRadians(lat2)) *
                sin(dLon / 2) * sin(dLon / 2)
        val c = 2 * atan2(sqrt(a), sqrt(1 - a))
        return R * c
    }
}