package aquadx.sega.chusan.handler

import ext.*
import aquadx.sega.chusan.ChusanController
import aquadx.sega.chusan.model.request.Chu3UserAll
import aquadx.sega.chusan.model.userdata.*
import aquadx.sega.general.model.CardStatus
import aquadx.sega.general.model.UserRecentRating
import kotlinx.serialization.encodeToString

@Suppress("UNCHECKED_CAST")
fun ChusanController.upsertApiInit() {
    "UpsertUserChargelog" {
        val charge = parsing { mapper.convert<UserCharge>(data["userCharge"] as JDict) }
        charge.user = db.userData.findByCard_ExtId(uid) ?: (400 - "User not found")
        charge.id = db.userCharge.findByUser_Card_ExtIdAndChargeId(uid, charge.chargeId)?.id ?: 0
        db.userCharge.save(charge)
        charge.user.card?.let { cardService.updateCardTimestamp(it, "chu3") }
        """{"returnCode":"1"}"""
    }

    "UpsertUserAll" api@ {
        val req = parsing { mapper.convert<Chu3UserAll>(data["upsertUserAll"]!!) }

        req.run {
            // UserData
            val oldUser = db.userData.findByCard_ExtId(uid)
            val u = (userData?.get(0) ?: return@api null).apply {
                id = oldUser?.id ?: 0
                card = oldUser?.card ?: us.cardRepo.findByExtId(uid) ?: (404 - "Card not found")

                val version = data["version"] as? String ?: "0.00"
                val versionNumber = version.toDoubleOrNull() ?: 0.0
                userName = if (versionNumber >= 2.40) {
                    userName
                } else {
                    userName.fromChusanUsername()
                }
                userNameEx = ""
            }.also { db.userData.saveAndFlush(it) }

            // If the user was previously migrated to Minato, saving would mark them "migrated and then cleared".
            if (u.card?.status == CardStatus.MIGRATED_TO_MINATO) {
                u.card?.status = CardStatus.NORMAL_MIGRATED_TO_MINATO_AND_THEN_CLEARED
                us.cardRepo.save(u.card!!)
            }

            // Only save if it is a valid region and the user has played at least a song
            req.userPlaylogList?.firstOrNull()?.regionId?.let { rid ->
                val region = db.userRegions.findByUserAndRegionId(u, rid)?.apply {
                    playCount += 1
                } ?: UserRegions().apply {
                    user = u
                    regionId = rid
                }
                db.userRegions.save(region)
            }

            versionHelper.set(u.lastClientId, u.lastDataVersion)

            // Set users
            listOfNotNull(
                userPlaylogList, userGameOption, userMapAreaList, userCharacterList, userItemList,
                userMusicDetailList, userActivityList, userChargeList, userCourseList, userDuelList,
                userNetBattlelogList, userUnlockChallengeList
            ).flatten().forEach { it.user = u }

            // Ratings
            fun Iterable<UserRecentRating>.str() = joinToString(",") { "${it.musicId}:${it.difficultId}:${it.score}" }

            ls(
                userRecentRatingList to "recent_rating_list",
                userRatingBaseList to "rating_base_list",
                userRatingBaseHotList to "rating_hot_list",
                userRatingBaseNextList to "rating_next_list",
                userRatingBaseNewList to "rating_new_list"
            ).filter { it.first != null }.forEach { (list, key) ->
                val d = db.userGeneralData.findByUserAndPropertyKey(u, key)
                    ?: UserGeneralData().apply { user = u; propertyKey = key }
                db.userGeneralData.save(d.apply { propertyValue = list!!.str() })
            }

            val misc = db.userMisc.findSingleByUser(u) ?: Chu3UserMisc().apply { user = u }

            // Favorites
            userFavoriteMusicList?.filter { it.musicId != -1 }?.ifEmpty { null }?.let { list ->
                misc.favMusic = list.map { it.musicId }.mut
            }

            // Net battle data
            userNetBattleData?.getOrNull(0)?.let {
                misc.recentNbSelect = it.recentNBSelectMusicList.map { it.musicId }.mut
            }

            // Add the battle log songs to misc
            if (userNetBattlelogList != null) {
                val music = userMusicDetailList?.map { it.musicId } ?: emptyList()
                misc.recentNbMusic = (misc.recentNbMusic + music).distinct().takeLast(10).mut
            }
            db.userMisc.save(misc)

            // Playlog
            userPlaylogList?.let { db.userPlaylog.saveAll(it) }
            userNetBattlelogList?.let { db.netBattleLog.saveAll(it.mapApplyI { i ->
                userPlaylogList?.getOrNull(i)?.let {
                    musicId = it.musicId
                    difficultyId = it.level
                    score = it.score
                }

                val version = data["version"] as? String ?: "0.00"
                val versionNumber = version.toDoubleOrNull() ?: 0.0
                if (versionNumber < 2.40) {
                    // 2.40以下版本需要转换编码
                    selectUserName = selectUserName.fromChusanUsername()
                    opponentUserName1 = opponentUserName1.fromChusanUsername()
                    opponentUserName2 = opponentUserName2.fromChusanUsername()
                    opponentUserName3 = opponentUserName3.fromChusanUsername()
                }
            }) }

            // List data
            userGameOption?.get(0)?.let { obj ->
                db.userGameOption.saveAndFlush(obj.apply {
                    id = db.userGameOption.findSingleByUser(u)?.id ?: 0 }) }

            userMapAreaList?.let { list ->
                db.userMap.saveAll(list.distinctBy { it.mapAreaId }.mapApply {
                    id = db.userMap.findByUserAndMapAreaId(u, mapAreaId)?.id ?: 0 }) }

            userCharacterList?.let { list ->
                db.userCharacter.saveAll(list.distinctBy { it.characterId }.mapApply {
                    id = db.userCharacter.findByUserAndCharacterId(u, characterId)?.id ?: 0 }) }

            userItemList?.let { list ->
                db.userItem.saveAll(list.distinctBy { it.itemId to it.itemKind }.mapApply {
                    id = db.userItem.findByUserAndItemIdAndItemKind(u, itemId, itemKind)?.id ?: 0 }) }

            userMusicDetailList?.let { list ->
                db.userMusicDetail.saveAll(list.distinctBy { it.musicId to it.level }.mapApply {
                    id = db.userMusicDetail.findByUserAndMusicIdAndLevel(u, musicId, level)?.id ?: 0 }) }

            userActivityList?.let { list ->
                db.userActivity.saveAll(list.distinctBy { it.activityId to it.kind }.mapApply {
                    id = db.userActivity.findByUserAndActivityIdAndKind(u, activityId, kind)?.id ?: 0 }) }

            userChargeList?.let { list ->
                db.userCharge.saveAll(list.distinctBy { it.chargeId }.mapApply {
                    id = db.userCharge.findByUserAndChargeId(u, chargeId)?.id ?: 0 }) }

            userCourseList?.let { list ->
                db.userCourse.saveAll(list.distinctBy { it.courseId }.mapApply {
                    id = db.userCourse.findByUserAndCourseId(u, courseId)?.id ?: 0 }) }

            userDuelList?.let { list ->
                db.userDuel.saveAll(list.distinctBy { it.duelId }.mapApply {
                    id = db.userDuel.findByUserAndDuelId(u, duelId)?.id ?: 0 }) }

            userUnlockChallengeList?.let { list ->
                db.userChallenge.saveAll(list.distinctBy { it.unlockChallengeId }.mapApply {
                    id = db.userChallenge.findByUserAndUnlockChallengeId(u, unlockChallengeId)?.id ?: 0 }) }

            userLinkedVerseList?.let { list ->
                db.userLinkedVerse.saveAll(list.map{
                    it.apply{ it.user = u }
                }.distinctBy { it.linkedVerseId }.mapApply {
                    id = db.userLinkedVerse.findByUserAndLinkedVerseId(u, linkedVerseId)?.id ?: 0 })
                }

            // Need testing
//            userLoginBonusList?.let { list ->
//                db.userLoginBonus.saveAll(list.distinctBy { it["presetId"] as String }.map {
//                    val id = it["presetId"]!!.int
//                    (db.userLoginBonus.findLoginBonus(uid.int, 1, id) ?: UserLoginBonus()).apply {
//                        user = u.id.toInt()
//                        presetId = id
//                        lastUpdateDate = LocalDateTime.now()
//                        isWatched = true
//                    }
//                })
//            }

            req.userCMissionList?.forEach { d ->
                (db.userCMission.findByUser_Card_ExtIdAndMissionId(uid, d.missionId)
                    ?: UserCMission().apply {
                        missionId = d.missionId
                        user = u
                    }
                    ).apply { point = d.point }.also { db.userCMission.save(it) }

                d.userCMissionProgressList?.forEach inner@ { p ->
                    (db.userCMissionProgress.findByUser_Card_ExtIdAndMissionIdAndOrder(uid, d.missionId, p.order)
                        ?: UserCMissionProgress().apply {
                            missionId = d.missionId
                            order = p.order
                            user = u
                        }
                        ).apply {
                            progress = p.progress
                            stage = p.stage
                        }.also { db.userCMissionProgress.save(it) }
                }
            }

            u.card?.let { cardService.updateCardTimestamp(it, "chu3") }
        }

        """{"returnCode":1}"""
    }
}
