package kr.lunaticbum.back.lun.model

import org.bson.BsonType
import org.bson.codecs.pojo.annotations.BsonId
import org.bson.codecs.pojo.annotations.BsonRepresentation
import org.springframework.data.mongodb.core.index.CompoundIndex
import org.springframework.data.mongodb.core.index.Indexed
import org.springframework.data.mongodb.core.mapping.Document
import org.springframework.data.mongodb.repository.ReactiveMongoRepository
import org.springframework.stereotype.Repository
import org.springframework.stereotype.Service
import reactor.core.publisher.Mono
import java.time.ZonedDateTime
import jakarta.servlet.http.HttpServletRequest
import org.springframework.security.core.context.SecurityContextHolder
import java.time.LocalDate
import java.time.ZoneId
import java.time.temporal.TemporalAdjusters

@Service
class VisitorLogService(private val repository: VisitorLogRepository) {

    /**
     * 사용자의 방문을 기록합니다. (하루에 한 번만)
     */
    fun recordVisit(request: HttpServletRequest): Mono<Void> {
        val ipAddress = request.remoteAddr
        val user = SecurityContextHolder.getContext().authentication

        val today = LocalDate.now(ZoneId.systemDefault())
        val startOfDay = today.atStartOfDay(ZoneId.systemDefault()).toInstant().toEpochMilli()
        val endOfDay = today.plusDays(1).atStartOfDay(ZoneId.systemDefault()).toInstant().toEpochMilli()

        val hasVisitedToday: Mono<Boolean>
        val visitorLog: VisitorLog

        if (user != null && user.isAuthenticated && user.principal is org.springframework.security.core.userdetails.UserDetails) {
            val userDetails = user.principal as org.springframework.security.core.userdetails.UserDetails
            val userId = userDetails.username
            hasVisitedToday = repository.findFirstByUserIdAndVisitTimestampBetween(userId, startOfDay, endOfDay).hasElement()
            visitorLog = VisitorLog(ipAddress = ipAddress, userId = userId)
        } else {
            hasVisitedToday = repository.findFirstByIpAddressAndVisitTimestampBetween(ipAddress, startOfDay, endOfDay).hasElement()
            visitorLog = VisitorLog(ipAddress = ipAddress)
        }

        return hasVisitedToday.flatMap { visited ->
            if (!visited) {
                repository.save(visitorLog).then()
            } else {
                Mono.empty()
            }
        }
    }

    /**
     * 방문자 통계를 계산하여 반환합니다.
     */
    fun getVisitorStats(): Mono<VisitorStatsDto> {
        val now = LocalDate.now(ZoneId.systemDefault())

        val todayStart = now.atStartOfDay(ZoneId.systemDefault()).toInstant().toEpochMilli()
        val weekStart = now.with(TemporalAdjusters.previousOrSame(java.time.DayOfWeek.MONDAY)).atStartOfDay(ZoneId.systemDefault()).toInstant().toEpochMilli()
        val monthStart = now.withDayOfMonth(1).atStartOfDay(ZoneId.systemDefault()).toInstant().toEpochMilli()
        val yearStart = now.withDayOfYear(1).atStartOfDay(ZoneId.systemDefault()).toInstant().toEpochMilli()

        val todayCountMono = repository.countByVisitTimestampGreaterThanEqual(todayStart)
        val weekCountMono = repository.countByVisitTimestampGreaterThanEqual(weekStart)
        val monthCountMono = repository.countByVisitTimestampGreaterThanEqual(monthStart)
        val yearCountMono = repository.countByVisitTimestampGreaterThanEqual(yearStart)
        val totalCountMono = repository.count()

        return Mono.zip(todayCountMono, weekCountMono, monthCountMono, yearCountMono, totalCountMono)
            .map { tuple ->
                VisitorStatsDto(
                    today = tuple.t1,
                    week = tuple.t2,
                    month = tuple.t3,
                    year = tuple.t4,
                    total = tuple.t5
                )
            }
    }
}
// 방문 기록을 저장할 MongoDB 문서
@Document(collection = "VisitorLog")
// IP와 날짜, 또는 사용자와 날짜로 중복 조회를 빠르게 하기 위해 인덱스 추가
@CompoundIndex(name = "ip_timestamp_idx", def = "{'ipAddress': 1, 'visitTimestamp': -1}")
@CompoundIndex(name = "user_timestamp_idx", def = "{'userId': 1, 'visitTimestamp': -1}")
data class VisitorLog(
    @BsonId
    @BsonRepresentation(BsonType.OBJECT_ID)
    var id: String? = null,
    val ipAddress: String,
    val userId: String? = null, // 로그인 사용자일 경우 ID 저장
    @Indexed
    val visitTimestamp: Long = System.currentTimeMillis()
)

// API 응답으로 사용할 데이터 클래스
data class VisitorStatsDto(
    val today: Long,
    val week: Long,
    val month: Long,
    val year: Long,
    val total: Long
)

// VisitorLog 데이터 접근을 위한 리포지토리
@Repository
interface VisitorLogRepository : ReactiveMongoRepository<VisitorLog, String> {
    // 특정 IP가 특정 기간 내에 방문한 기록이 있는지 확인
    fun findFirstByIpAddressAndVisitTimestampBetween(ipAddress: String, start: Long, end: Long): Mono<VisitorLog>

    // 특정 사용자가 특정 기간 내에 방문한 기록이 있는지 확인
    fun findFirstByUserIdAndVisitTimestampBetween(userId: String, start: Long, end: Long): Mono<VisitorLog>

    // 특정 시간 이후의 방문 기록 수 계산
    fun countByVisitTimestampGreaterThanEqual(timestamp: Long): Mono<Long>
}