package kr.lunaticbum.back.lun.controllers

import com.google.gson.Gson
import com.google.protobuf.LazyStringArrayList.emptyList
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
import kotlinx.coroutines.reactor.awaitSingle
import kotlinx.coroutines.reactor.awaitSingleOrNull
import kr.lunaticbum.back.lun.configs.core.GlobalEnvironment
import kr.lunaticbum.back.lun.configs.core.GlobalEnvironment.Companion.ApiKeyWordKey
import kr.lunaticbum.back.lun.configs.core.GlobalEnvironment.Companion.EncType11
import kr.lunaticbum.back.lun.configs.core.GlobalEnvironment.Companion.EncTypeKey
import kr.lunaticbum.back.lun.model.*
import kr.lunaticbum.back.lun.utils.JwtUtil
import kr.lunaticbum.back.lun.utils.LogService
import kr.lunaticbum.back.lun.utils.extractModelData
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.data.domain.PageRequest
import org.springframework.http.MediaType
import org.springframework.http.ResponseCookie
import org.springframework.http.ResponseEntity
import org.springframework.security.access.prepost.PreAuthorize
import org.springframework.security.authentication.AuthenticationManager
import org.springframework.security.core.userdetails.UserDetails
import org.springframework.web.bind.annotation.*
import org.springframework.web.reactive.function.client.WebClient
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.core.Authentication
import org.springframework.security.core.annotation.AuthenticationPrincipal
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.security.web.authentication.RememberMeServices
import org.springframework.security.web.context.HttpSessionSecurityContextRepository
import reactor.core.publisher.Mono
import java.io.File
import java.time.Instant
import java.time.ZoneId
import java.time.format.DateTimeFormatter
import kr.lunaticbum.back.lun.model.Message
import kr.lunaticbum.back.lun.service.CommentService
import kr.lunaticbum.back.lun.service.PostHistoryManager
import kr.lunaticbum.back.lun.service.PostManager
import kr.lunaticbum.back.lun.service.WebBookmarkService
import org.springframework.stereotype.Controller

@Controller
@RequestMapping("/user")
class UserController(
    private val rememberMeServices: RememberMeServices,
    private val userManager: UserManager,
    private val postManager: PostManager,
    private val commentService: CommentService,
    private val gameRankService: GameRankService, // [신규 추가] GameRankService 의존성 주입
    private val messageService: MessageService,
    private val webBookmarkService: WebBookmarkService,
    private val imageMetaService: ImageMetaService,
    private val jwtUtil: JwtUtil,
    private val migrationService: MigrationService,
    private val postHistoryManager: PostHistoryManager
) {


    @Autowired
    lateinit var globalEvv : GlobalEnvironment

    @Autowired
    lateinit var logService: LogService

    // [추가] 게시물 히스토리 조회 페이지 (관리자 전용)
    @GetMapping("/admin/posts/{postId}/history")
    @PreAuthorize("hasRole('ADMIN')")
    suspend fun postHistoryPage(@PathVariable postId: String): ResultMV {
        val vm = ResultMV("content/user/post_history") // 새 템플릿 파일을 렌더링

        // 1. 현재 버전의 게시물 조회
        val currentPost = postManager.findById(postId).awaitSingleOrNull()
        if (currentPost == null) {
            // 게시물이 없으면 리디렉션 또는 에러 처리
            return ResultMV("redirect:/user/info")
        }

        // 2. PostHistoryManager를 통해 히스토리 목록 조회
        val historyList = postHistoryManager.findByPostId(postId).collectList().awaitSingle()

        vm.modelMap["currentPost"] = currentPost
        vm.modelMap["historyList"] = historyList
        vm.setTitle("'${currentPost.title}' 수정 히스토리")
        return vm
    }
    @GetMapping("join.bs")
    fun hello(httpServletRequest: HttpServletRequest): ResultMV {
        logService.log("onJoin")
        val vm = ResultMV("content/user/join")
//        when(System.currentTimeMillis() % 5L) {
//            0L -> vm.modelMap.put(EncTypeKey,"T4")
//            1L ->    vm.modelMap.put(EncTypeKey,"T3")
//            2L ->    vm.modelMap.put(EncTypeKey,"T2")
//            else -> vm.modelMap.put(EncTypeKey,"T0")
//        }

        val file = File("/")

        logService.log(file.absolutePath)
        vm.modelMap.put(EncTypeKey,EncType11)
        vm.modelMap.put(ApiKeyWordKey,"JOIN")
        return vm
    }

    @GetMapping("login.bs")
    fun userLogin(httpServletRequest: HttpServletRequest): ResultMV {
        logService.log("onJoin")
        val vm = ResultMV("content/user/login")
        vm.modelMap.put(EncTypeKey,EncType11)
        vm.modelMap.put(ApiKeyWordKey,"LOGIN")
        return vm
    }

//    @GetMapping("logout.bs")
//    fun logoutBs(session: HttpSession): ResultMV {
//        session.invalidate(); // 세션 날리기 (로그아웃)
//        return "redirect:/"; // 홈으로 이동
//    }

    @Autowired
    lateinit var authenticationManager: AuthenticationManager


    @ResponseBody
    @PostMapping("login.bjx")
    fun login(httpServletRequest: HttpServletRequest,
              httpRes: HttpServletResponse,
              @RequestBody jsonString: String) : ResponseEntity<LoginResult> {
        var loginResult = LoginResult().apply {
            this.isOk = false
            this.resultCode = 9999
            this.resultMsg = "JUST INITIALIZED"
        }
        try {
            var lResultCode = 0
            var lResultMsg = "Suscces"
            var authResult : Authentication? = null
            jsonString.extractModelData { exception, originDataString ->
                if (exception == null) {
                    logService.log("originDataString >>> $originDataString")
                    val target = Gson().fromJson(originDataString, User::class.java) ?: User()
                    try {
                        val authToken = UsernamePasswordAuthenticationToken(target.user_id, target.user_pw)
                        authResult = authenticationManager.authenticate(authToken)  // 인증 시도
                        println("authResult >>>> $authResult")
                        // 인증 성공 시 SecurityContextHolder에 인증 정보 저장
                        SecurityContextHolder.getContext().authentication = authResult


                        // 인증 정보가 담긴 SecurityContext를 세션에 저장
                        val session = httpServletRequest.getSession(true)
                        session.setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, SecurityContextHolder.getContext())
                        println("authResult >>>> LOGIN OK")

                        val principal = authResult?.principal
                        if (principal is UserDetails) {
                            val token = jwtUtil.generateToken(principal)
                            loginResult.token = token // 2. 응답 객체에 토큰 추가


                            println("target.remeberMe >>> ${target.rememberMe}")
                            loginResult.rememberMe = target.rememberMe
                            if (target.rememberMe == true) {
                                rememberMeServices.loginSuccess(httpServletRequest, httpRes, authResult)
                            }
                        } else {
                            lResultMsg = "is wrong infomation id or passord"
                            lResultCode = 7100
                        }
                    } catch (e: Exception) {
                        e.printStackTrace()
                    }
                } else {
                    exception.printStackTrace()
                    lResultMsg = exception.message ?: "unknown exception"
                    lResultCode = 7999
                }
            }
            val responce = ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).headers {

            }.body(loginResult.apply {
                this.isOk = lResultCode == 0
                this.resultCode = lResultCode
                this.resultMsg = lResultMsg
            }).apply {

            }

            return responce
        }catch (e: Exception){
            return ResponseEntity.internalServerError().contentType(MediaType.APPLICATION_JSON).headers {

            }.body(LoginResult().apply {
                this.isOk = false
                this.resultCode = -999
                this.resultMsg = e.message ?: "unknown exception"
            }).apply {


            }
        }
    }

    @PostMapping("/api/user/theme")
    @ResponseBody
    fun updateTheme(
        @RequestBody request: Map<String, String>,
        @AuthenticationPrincipal user: UserDetails?
    ): Mono<ResponseEntity<String>> {
        if (user == null) return Mono.just(ResponseEntity.ok("Guest theme saved locally"))

        val newTheme = request["theme"] ?: "default"

        return userManager.findById(user.username).flatMap { dbUser ->
            dbUser.theme = newTheme
            userManager.save(dbUser)
        }.map {
            ResponseEntity.ok("Theme updated to $newTheme")
        }
    }

    private fun setTokenToCookie(tokenPrefix: String, token: String, maxAgeSeconds: Long): ResponseCookie {
        return ResponseCookie.from(tokenPrefix, token)
            .path("/")
            .maxAge(maxAgeSeconds)
            .httpOnly(true)
            .sameSite("None")
            .secure(true)
            .build()
    }

    @ResponseBody
    @PostMapping("logout.bjx")
    fun logout(httpServletRequest: HttpServletRequest) : ResponseEntity<ResponceResult> {
        val responce = ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body(ResponceResult().apply {

        })
        return responce
    }

    @ResponseBody
    @PostMapping("joinUser.bjx")
    fun joinUser(httpServletRequest: HttpServletRequest, @RequestBody jsonString: String) : ResponseEntity<ResponceResult> {
        logService.log("${httpServletRequest.requestURI}")
        logService.log(jsonString)
        var lResultCode = 0
        var lResultMsg = "Suscces"
        var u : User? = null
        jsonString.extractModelData { exception, s ->
            if (exception == null) {
                val user = Gson().fromJson(s, User::class.java) ?: User()
                if (user.checkValid() == false) {
                    lResultCode = 7009
                    lResultMsg = "user insert Fail  Reason : Not Correct Data"
                }else if (userManager.findById(user!!.user_id!!)?.block() != null) {
                    lResultCode = 7001
                    lResultMsg = "user insert Fail  Reason : already has Same Id"
                }  else {
                    u = userManager.save(user).block()
                }
            } else {
                exception.printStackTrace()
                lResultMsg = exception.message ?: "unknown exception"
                lResultCode = 7999
            }
        }
        val responce = ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body(ResponceResult().apply {
            this.resultCode = lResultCode
            this.resultMsg = lResultMsg
        })
        return responce
    }

    @ResponseBody
    @GetMapping("test/{path}")
    fun test(@PathVariable path : String): String {
        logService.log("path >>> $path")
        val client = WebClient.create()
        return client.get()
            .uri("https://api.telegram.org/${globalEvv.telegramBotKey}/sendMessage?chat_id=${globalEvv.telegramMyId}&text=${path}")
            .retrieve()
            .bodyToMono(String::class.java).block() ?: "FAIL"
    }

    @PostMapping("/admin/migrate-posts")
    @PreAuthorize("hasRole('ADMIN')")
    fun runPostMigration(): Mono<ResponseEntity<MigrationReport>> {
        return migrationService.migratePosts()
            .map { report -> ResponseEntity.ok(report) }
            .onErrorResume { e -> // 에러 발생 시
                val errorReport = MigrationReport(0, 0, 0, listOf(e.message ?: "Unknown error"))
                Mono.just(ResponseEntity.status(500).body(errorReport))
            }
    }

    /**
     * [수정] '내 정보' 페이지를 위한 핸들러 (게임 랭킹 조회 추가)
     */
    @GetMapping("/info")
    suspend fun myInfoPage(@AuthenticationPrincipal userDetails: UserDetails?): ResultMV {
        if (userDetails == null) {
            return ResultMV("redirect:/home.bs?action=login")
        }
        val vm = ResultMV("content/user/my_info")
        val username = userDetails.username

        // 1. 기본 유저 정보 조회
        val user = userManager.findById(username)?.block()
        if (user != null) {
            // 가입일을 보기 좋은 형식으로 변환하여 모델에 추가
            val joinDate = Instant.ofEpochMilli(user.user_join)
                .atZone(ZoneId.systemDefault())
                .toLocalDate()
            vm.modelMap["user"] = user
            vm.modelMap["joinDate"] = joinDate.format(DateTimeFormatter.ofPattern("yyyy년 MM월 dd일"))
        }

        // 2. 내가 쓴 글 목록 조회 (최신 10개)
        val myPosts = postManager.findPostsByWriter(username, PageRequest.of(0, 10)).collectList().block()
        vm.modelMap["myPosts"] = myPosts ?: emptyList()

        // 사용자가 저장한 모든 북마크 목록을 가져옵니다.
        val myBookmarks = webBookmarkService.getBookmarksForUser(username) // 1. 모든 북마크를 Flux로 가져옴
            .collectList() // 2. Flux 스트림을 Mono<List<WebBookmark>>으로 변환
            .block() // 3. 최종적으로 List<WebBookmark>으로 변환

        // 모델에 "myBookmarks" 라는 키로 저장된 북마크 리스트를 추가합니다.
        vm.modelMap["myBookmarks"] = myBookmarks ?: emptyList()

        // 3. 내가 쓴 댓글 목록 조회 (최신 10개)
        val myComments = commentService.findCommentsByWriter(username, PageRequest.of(0, 10)).collectList().block()
        vm.modelMap["myComments"] = myComments ?: emptyList()

        // [신규] 받은 쪽지와 보낸 쪽지를 모두 조회하고, 시간순으로 합쳐서 모델에 추가합니다.
        val receivedMessages : List<Message> = (messageService.getMessagesForUser(username).collectList().block() ?: emptyList()) as List<Message>
        val sentMessages : List<Message> = (messageService.getSentMessagesByUser(username).collectList().block() ?: emptyList()) as List<Message>

        // 두 리스트를 합친 후, 최신순으로 정렬합니다.
        val allMessages = (receivedMessages + sentMessages).sortedByDescending { it.timestamp }
        vm.modelMap["myMessages"] = allMessages

        // 4. [신규 추가] 내가 남긴 게임 랭킹 조회 (최신 20개)
        val myRanks = gameRankService.getRanksByPlayer(username).take(20).collectList().block()
        vm.modelMap["myRanks"] = myRanks ?: emptyList()

        vm.modelMap["pageTitle"] = "내 정보" // 동적 페이지 제목 설정

        val isAdmin = userDetails?.authorities?.any { it.authority == "ROLE_ADMIN" } == true
        vm.modelMap["isAdmin"] = isAdmin

        if (isAdmin) {
            // 관리자일 경우, 추가 정보 조회
            vm.modelMap["allUsers"] = userManager.findAllUsers().collectList().block()
            vm.modelMap["permissionRequests"] = userManager.findUsersRequestingWritePermission().collectList().block()
            vm.modelMap["allRecentPosts"] = postManager.findAllVersionsPaginated(PageRequest.of(0, 20)).block() // 모든 글 조회
            vm.modelMap["allImages"] = imageMetaService.getAllImages().collectList().block()
            // [신규 추가] 사이트 소개글 히스토리를 모델에 추가
            vm.modelMap["aboutPostHistory"] = postManager.findAboutPostHistory().collectList().block()
        }

        return vm
    }

    // [신규] 글쓰기 권한 요청 API
    @PostMapping("/request-write")
    @ResponseBody
    fun requestWrite(@AuthenticationPrincipal userDetails: UserDetails?): Mono<ResponseEntity<String>> {
        if (userDetails == null) {
            return Mono.just(ResponseEntity.status(401).build())
        }

        return userManager.requestWritePermission(userDetails.username)
            .map { savedUser -> // DB에 저장된 유저 정보를 받음
                // --- 텔레그램 알림 전송 로직 ---
                try {
                    val message = "[권한 요청] 사용자 '${savedUser.user_id}'님이 글쓰기 권한을 요청했습니다."
                    val client = WebClient.create()
                    client.get()
                        .uri("https://api.telegram.org/${globalEvv.telegramBotKey}/sendMessage?chat_id=${globalEvv.telegramMyId}&text=${message}")
                        .retrieve()
                        .bodyToMono(String::class.java)
                        .subscribe( // non-blocking (Fire-and-Forget) 방식으로 호출
                            { response -> logService.log("Telegram notification sent successfully for user ${savedUser.user_id}. Response: $response") },
                            { error -> logService.log("Error sending Telegram notification for user ${savedUser.user_id}: ${error.message}") }
                        )
                } catch (e: Exception) {
                    // WebClient 생성 또는 설정 중 발생할 수 있는 동기적 예외 처리
                    logService.log("Exception while preparing Telegram notification for user ${savedUser.user_id}: ${e.message}")
                }
                // --- 알림 로직 끝 ---

                // 기존과 동일하게 클라이언트에게 성공 응답을 반환
                ResponseEntity.ok("요청이 완료되었습니다.")
            }
            .defaultIfEmpty(ResponseEntity.status(404).body("사용자를 찾을 수 없습니다."))
    }


    // [신규] 글쓰기 권한 승인 API (관리자 전용)
    @PostMapping("/approve-writer/{userId}")
    @PreAuthorize("hasRole('ADMIN')")
    @ResponseBody
    fun approveWriter(@PathVariable userId: String): Mono<ResponseEntity<User>> {
        return userManager.approveWritePermission(userId)
            .map { ResponseEntity.ok(it) }
            .defaultIfEmpty(ResponseEntity.notFound().build())
    }

    // [신규] 글쓰기 권한 거절 API (관리자 전용)
    @PostMapping("/reject-writer/{userId}")
    @PreAuthorize("hasRole('ADMIN')")
    @ResponseBody
    fun rejectWriter(@PathVariable userId: String): Mono<ResponseEntity<User>> {
        return userManager.rejectWritePermission(userId)
            .map { ResponseEntity.ok(it) }
            .defaultIfEmpty(ResponseEntity.notFound().build())
    }


    /**
     * 북마크 저장을 위해 클라이언트로부터 받는 데이터를 담는 DTO
     */
    data class BookmarkSaveRequest(
        val url: String,
        val title: String?,
        val description: String?,
        val thumbnailUrl: String?,
        val userComment: String?,
        val visibility: String?
    )

    @PostMapping("/bookmarks/save")
    @ResponseBody
    fun saveBookmark(
        // [수정] DTO 대신 URL과 코멘트만 간단히 받도록 변경
        @RequestBody request: Map<String, String>,
        @AuthenticationPrincipal user: UserDetails?
    ): Mono<ResponseEntity<WebBookmark>> {
        if (user == null) {
            return Mono.just(ResponseEntity.status(401).build())
        }

        val url = request["url"] ?: return Mono.just(ResponseEntity.badRequest().build())

        // [수정] URL과 사용자 정보, PENDING 상태만으로 북마크 객체를 생성하여 저장
        val newBookmark = WebBookmark(
            userId = user.username,
            url = url,
            userComment = request["userComment"],
            visibility = request["visibility"] ?: Visibility.PRIVATE.name,
            userSelectedImageUrl = request["userSelectedImageUrl"],
            metadataStatus = MetadataStatus.PENDING.name // 초기 상태는 PENDING

        )

        // DB에 저장하고 즉시 사용자에게 성공 응답을 반환
        return webBookmarkService.saveBookmark(newBookmark)
            .map { savedBookmark -> ResponseEntity.ok(savedBookmark) }
    }

}


// --- API 요청/응답을 위한 DTO ---
data class LoginRequest(val userId: String, val userPw: String)
data class LoginResponse(val token: String)

@RestController
@RequestMapping("/api/auth")
class AuthController(
    private val authenticationManager: AuthenticationManager,
    private val userManager: UserManager,
    private val jwtUtil: JwtUtil,
    private val logService: LogService
) {
    @PostMapping("/login")
    fun createAuthenticationToken(@RequestBody loginRequest: LoginRequest): ResponseEntity<*> {
        // 1. 사용자 인증
        authenticationManager.authenticate(
            UsernamePasswordAuthenticationToken(loginRequest.userId, loginRequest.userPw)
        )
        logService.log("loginRequest.userId >>> ${loginRequest.userId}")
        // 2. 인증 성공 시 UserDetails 객체 로드
        val userDetails = userManager.loadUserByUsername(loginRequest.userId)
        logService.log("userDetails.username >>> ${userDetails.username}")
        // 3. JWT 토큰 생성
        val token = jwtUtil.generateToken(userDetails)

        // 4. 토큰을 응답으로 반환
        return ResponseEntity.ok(LoginResponse(token))
    }
}