package kr.lunaticbum.back.lun.service

import kr.lunaticbum.back.lun.model.Post
import kr.lunaticbum.back.lun.model.PostType
import kr.lunaticbum.back.lun.repository.PostRepository
import kr.lunaticbum.back.lun.utils.LogService
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.data.domain.PageRequest
import org.springframework.data.domain.Pageable
import org.springframework.data.mongodb.core.FindAndModifyOptions
import org.springframework.data.mongodb.core.ReactiveMongoTemplate
import org.springframework.data.mongodb.core.query.Criteria
import org.springframework.data.mongodb.core.query.Query
import org.springframework.data.mongodb.core.query.Update
import org.springframework.security.crypto.password.PasswordEncoder
import org.springframework.stereotype.Service
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
import java.net.URLDecoder
import java.text.SimpleDateFormat
import java.util.Date


@Service
class PostManager(
    private val postRepository: PostRepository,
    private val reactiveMongoTemplate: ReactiveMongoTemplate
)  {
    @Autowired
    private lateinit var logService: LogService

    @Autowired
    private lateinit var bCryptPasswordEncoder: PasswordEncoder

    fun deletePost(postId: String): Mono<Void> {
        return postRepository.deleteById(postId)
    }

    // [수정] 익명 사용자용 목록 조회 (Aggregation 사용)
    fun findLatestUniquePaginated(pageable: Pageable) : Mono<List<Post>> {
        return postRepository.findLatestUniquePublishedPaginated(pageable)
            .collectList()
    }

    // [수정] 익명 사용자용 글 개수 (Aggregation 사용)
    fun countLatestUnique(): Mono<Long> {
        return postRepository.countLatestUniquePublished()
            .map { it.totalCount }
            .switchIfEmpty(Mono.just(0L))
    }

    // [수정] '글쓰기' 권한 사용자용 목록 조회 (Aggregation 사용)
    fun findLatestUniqueForWriter(username: String, pageable: Pageable) : Mono<List<Post>> {
        return postRepository.findLatestUniqueForWriterPaginated(username, pageable)
            .collectList()
    }

    // [수정] '글쓰기' 권한 사용자용 글 개수 (Aggregation 사용)
    fun countLatestUniqueForWriter(username: String): Mono<Long> {
        return postRepository.countLatestUniqueForWriter(username)
            .map { it.totalCount }
            .switchIfEmpty(Mono.just(0L))
    }

    // [수정] 익명 사용자용 인기글
    fun getTop5UniquePublishedByViews(): Flux<Post> {
        return postRepository.findTop5ByPostingIsTrueOrderByReadCountDesc().map { p ->
            p.title = URLDecoder.decode(p.title, "UTF-8")
            if (p.title?.isEmpty() == true) {
                p.title = "무제(無題)"
            }
            p
        }
    }

    // [수정] 익명 사용자용 최신글
    fun getRecent5UniquePublished(): Flux<Post> {
        return postRepository.findTop5ByPostingIsTrueOrderByModifyTimeDesc().map {
                p ->
            p.title = URLDecoder.decode(p.title, "UTF-8")
            if (p.title?.isEmpty() == true) {
                p.title = "무제(無題)"
            }
            p
        }
    }

    // --- [신규 추가] 카테고리/태그 관련 서비스 메소드 ---
    fun findAllDistinctCategories(): Flux<String> {
        // 'category' 필드가 null이 아니고 비어있지 않은 문서들을 대상으로 distinct 연산 수행
        val query = Query.query(Criteria.where("category").ne(null).ne(""))
        return reactiveMongoTemplate.findDistinct(query, "category", "Post", String::class.java)
    }

    fun findAllDistinctTags(): Flux<String> {
        return postRepository.findDistinctTags()
            .mapNotNull { doc -> doc.getString("_id") } // Document에서 실제 태그 문자열("_id" 필드)을 추출
            .filter { it.isNotBlank() } // 만약을 위해 한 번 더 빈 값 필터링
    }
    // --- [신규 추가] 필터링된 게시물 목록 조회 서비스 메소드 ---
    fun findPostsByCategory(category: String, pageable: Pageable): Mono<List<Post>> {
        return postRepository.findByCategoryAndPostingIsTrueOrderByModifyTimeDesc(category, pageable).collectList()
    }
    fun countPostsByCategory(category: String): Mono<Long> {
        return postRepository.countByCategoryAndPostingIsTrue(category)
    }

    fun findPostsByTag(tag: String, pageable: Pageable): Mono<List<Post>> {
        // [수정] 한글 및 다국어를 지원하는 정규식으로 변경
        val regex = "(^|,)${Regex.escape(tag)}(,|$)"
        return postRepository.findByTagsRegexAndPostingIsTrueOrderByModifyTimeDesc(regex, pageable).collectList()
    }

    fun countPostsByTag(tag: String): Mono<Long> {
        // [수정] 위와 동일하게 정규식 변경
        val regex = "(^|,)${Regex.escape(tag)}(,|$)"
        return postRepository.countByTagsRegexAndPostingIsTrue(regex)
    }

    // [신규 추가] 랜덤 Gibberish 포스트를 가져오는 서비스 메소드
    fun findRandomGibberish(): Mono<Post> {
        return postRepository.findRandomPublishedPostByType(PostType.GIBBERISH.name)
    }

    // [신규 추가] 가장 최신 '사이트 소개' 글을 찾는 메소드
    fun findLatestAboutPost(): Mono<Post> {
        // 'ABOUT_SITE' 타입의 글들을 최신순으로 정렬하여 첫 번째 것만 가져옴
        return postRepository.findByPostTypeOrderByModifyTimeDesc(PostType.ABOUT_SITE.name)
            .next() // Flux에서 첫 번째 아이템(Mono)을 반환
    }

    // [신규 추가] '사이트 소개' 글의 모든 버전(히스토리)을 찾는 메소드
    fun findAboutPostHistory(): Flux<Post> {
        return postRepository.findByPostTypeOrderByModifyTimeDesc(PostType.ABOUT_SITE.name)
    }

    // [신규] 게시물 차단
    fun blockPost(postId: String): Mono<Post> {
        return postRepository.findById(postId).flatMap { post ->
            post.isBlocked = true
            postRepository.save(post)
        }
    }

    // [신규] 게시물 차단 해제
    fun unblockPost(postId: String): Mono<Post> {
        return postRepository.findById(postId).flatMap { post ->
            post.isBlocked = false
            postRepository.save(post)
        }
    }

    fun findById(id: String): Mono<Post> {
        return postRepository.findById(id)
    }


    fun findPostsByWriter(writer: String, pageable: Pageable): Flux<Post> {
        return postRepository.findByWriterOrderByModifyTimeDesc(writer, pageable)
            .map { post ->
                post.title = post.title?.let { URLDecoder.decode(it, "UTF-8") } ?: ""
                if (post.title.isNullOrBlank()) {
                    val sdf = SimpleDateFormat("yyyy-MM-dd HH:mm")
                    post.title = "무제(無題) [${sdf.format(Date(post.writeTime))}]"
                }
                post
            }
    }


    fun getPost(id: String): Mono<Post> {
        val query = Query.query(Criteria.where("id").`is`(id))
        val update = Update().inc("readCount", 1)

        // 이 메서드는 기본값(returnNew=false)를 사용하여, 증가되기 *전*의 문서를 반환합니다.
        // (뷰어 로딩과 동시에 DB 카운트만 1 증가시킴)
        return reactiveMongoTemplate.findAndModify(query, update, Post::class.java)
            .switchIfEmpty(Mono.error(NoSuchElementException("Post not found with id $id")))
    }


    /**
     * 인증된 사용자를 위한 메서드 (모든 버전 조회, GIBBERISH 제외)
     */
    fun findAllVersionsPaginated(pageable :Pageable) : Mono<List<Post>> {
        return postRepository.findByPostTypeNotOrderByModifyTimeDesc(PostType.GIBBERISH.name, pageable)
            .map { post ->
                // 1. 제목을 UTF-8로 디코딩합니다.
                post.title = post.title?.let { URLDecoder.decode(it, "UTF-8") } ?: ""

                // 2. 제목이 비어있으면 작성 시간을 기반으로 기본 제목을 설정합니다.
                if (post.title.isNullOrBlank()) {
                    val sdf = SimpleDateFormat("yyyy-MM-dd HH:mm")
                    post.title = "무제(無題) [${sdf.format(Date(post.writeTime))}]"
                }
                post // 수정된 post 객체를 반환
            }
            .collectList()
    }

    /**
     * 인증된 사용자가 보는 글의 총 개수 (GIBBERISH 제외)
     */
    fun countAllVersions(): Mono<Long> {
        return postRepository.countByPostTypeNot(PostType.GIBBERISH.name)
    }

    /**
     * 좋아요 카운트를 1 증가시키고, JS에서 즉시 업데이트할 수 있도록 *업데이트된* 문서를 반환합니다.
     */
    fun incrementVote(postId: String): Mono<Post> {
        val query = Query.query(Criteria.where("id").`is`(postId))
        val update = Update().inc("voteCount", 1)
        // options().returnNew(true) : 업데이트된 후의 새 문서를 반환하도록 설정
        val options = FindAndModifyOptions.options().returnNew(true)
        return reactiveMongoTemplate.findAndModify(query, update, options, Post::class.java)
    }

    /**
     * 싫어요 카운트를 1 증가시키고, *업데이트된* 문서를 반환합니다.
     */
    fun incrementUnlike(postId: String): Mono<Post> {
        val query = Query.query(Criteria.where("id").`is`(postId))
        val update = Update().inc("unlikeCount", 1)
        val options = FindAndModifyOptions.options().returnNew(true)
        return reactiveMongoTemplate.findAndModify(query, update, options, Post::class.java)
    }


    fun getTop10Posts(): Flux<Post>  {
        return postRepository.findTop5ByOrderByReadCountDesc().map { p ->
            p.title = URLDecoder.decode(p.title)
            if (p.title?.isEmpty() == true) {
                p.title = "무제(無題)"
            }
            println(p.title)
            p
        }
    }

    fun getRecent10Posts(): Flux<Post>  {
        return postRepository.findTop5ByOrderByModifyTimeDesc().map { p ->
            p.title = URLDecoder.decode(p.title)
            if (p.title?.isEmpty() == true) {
                p.title = "무제(無題)"
            }
            println(p.title)
            p
        }

    }


    /**
     * 홈 화면은 이제 "익명 사용자용 최신 글"의 0번 페이지, 8개 아이템을 명시적으로 요청합니다.
     */
    fun find8() : Mono<List<Post>> {
        val pageRequest = PageRequest.of(0, 8) // Page 0, Size 8
        return this.findLatestUniquePaginated(pageRequest)
    }

    fun save(post: Post): Mono<Post> {
        println("saved user before ${post}")
//        user.hashPassword(bCryptPasswordEncoder)
        return postRepository.save(post)
            .doOnSuccess { savedPost ->
                // 저장이 완료되었을 때 실행될 로직 (로그 출력 등)
                println("saved post success: ${savedPost.id}")
            }
    }

    // [기존] 로그인 사용자용 인기글 (메서드 이름 명확화: getTop5 -> getTop5AllVersions)
    fun getTop5AllVersionsByViews(): Flux<Post>  {
        return postRepository.findTop5ByOrderByReadCountDesc().map { p ->
            p.title = URLDecoder.decode(p.title, "UTF-8")
            if (p.title?.isEmpty() == true) {
                p.title = "무제(無題)"
            }
            p
        }
    }

    // [기존] 로그인 사용자용 최신글 (메서드 이름 명확화)
    fun getRecent5AllVersions(): Flux<Post>  {
        return postRepository.findTop5ByOrderByModifyTimeDesc().map { p ->
            p.title = URLDecoder.decode(p.title, "UTF-8")
            if (p.title?.isEmpty() == true) {
                p.title = "무제(無題)"
            }
            p
        }
    }
}
