package test

import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.ObjectMapper
import ext.JACKSON
import icu.samnyan.aqua.net.games.chu3.Chu3DataExport
import icu.samnyan.aqua.net.games.mai2.Maimai2DataExport
import icu.samnyan.aqua.net.games.ongeki.OngekiDataExport
import icu.samnyan.aqua.net.games.wacca.WaccaDataExport
import icu.samnyan.aqua.sega.chusan.model.userdata.Chu3UserData
import icu.samnyan.aqua.sega.chusan.model.userdata.UserPlaylog as Chu3UserPlaylog
import icu.samnyan.aqua.sega.general.model.Card
import icu.samnyan.aqua.sega.maimai2.model.userdata.Mai2UserDetail
import icu.samnyan.aqua.sega.maimai2.model.userdata.Mai2UserMap
import icu.samnyan.aqua.sega.maimai2.model.userdata.Mai2UserPlaylog
import icu.samnyan.aqua.sega.ongeki.model.UserData as OngekiUserData
import icu.samnyan.aqua.sega.ongeki.model.UserPlaylog as OngekiUserPlaylog
import icu.samnyan.aqua.sega.util.BASIC_MAPPER
import icu.samnyan.aqua.sega.util.STRING_MAPPER
import icu.samnyan.aqua.sega.wacca.model.db.WcUserPlayLog
import icu.samnyan.aqua.sega.wacca.model.db.WaccaUser
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
import tools.jackson.databind.json.JsonMapper
import tools.jackson.module.kotlin.KotlinModule
import java.time.LocalDateTime
import java.util.Date

class ReproduceIssueTest {

    private fun loadResourceJson(path: String): String {
        return javaClass.getResourceAsStream(path)?.bufferedReader()?.use { it.readText() }
            ?: error("Test resource $path not found")
    }

    private fun sampleMai2Playlog() = Mai2UserPlaylog().apply {
        musicId = 11479
        level = 2
        achievement = 1000000
        deluxscore = 2000
        isClear = true
        isTap = true
        isHold = true
        isSlide = true
        isTouch = true
        isBreak = true
        isCriticalDisp = true
        isFastLateDisp = true
        isAchieveNewRecord = true
        isDeluxscoreNewRecord = true
        isPlayTutorial = true
        isEventMode = false
        isFreedomMode = false
        isNewFree = true
    }

    private fun sampleMai2Map() = Mai2UserMap().apply {
        mapId = 1
        distance = 100
        isLock = false
        isClear = true
        isComplete = true
    }

    private fun sampleMai2Export() = Maimai2DataExport().apply {
        userData = Mai2UserDetail().apply {
            userName = "TestUser"
            cmLastEmoneyBrand = 2
            cmLastEmoneyCredit = 69
        }
        userPlaylogList = listOf(sampleMai2Playlog())
        userMapList = listOf(sampleMai2Map())
    }

    private fun sampleChu3Export() = Chu3DataExport().apply {
        userData = Chu3UserData().apply { userName = "ChuUser" }
        userPlaylogList = listOf(Chu3UserPlaylog().apply {
            musicId = 100
            level = 1
            score = 1000000
            isClear = true
            isFullCombo = true
            isAllJustice = true
        })
    }

    private fun sampleOngekiExport() = OngekiDataExport().apply {
        userData = OngekiUserData().apply { userName = "OngekiUser" }
        userPlaylogList = listOf(OngekiUserPlaylog().apply {
            musicId = 200
            level = 1
            techScore = 1000000
            isFullBell = true
            isFullCombo = true
            isAllBreak = true
        })
    }

    private fun sampleWaccaExport() = WaccaDataExport(
        userData = WaccaUser().apply {
            userName = "WaccaUser"
            firstPlayDate = Date(0)
            lastPlayDate = Date(0)
            card = Card().apply {
                registerTime = LocalDateTime.of(2026, 1, 1, 0, 0)
                accessTime = LocalDateTime.of(2026, 1, 1, 0, 0)
            }
        },
        userOptionList = emptyList(),
        userBingoList = emptyList(),
        userFriendList = emptyList(),
        userGateList = emptyList(),
        userItemList = emptyList(),
        userBestScoreList = emptyList(),
        userPlaylogList = listOf(WcUserPlayLog().apply {
            musicId = 300
            level = 1
            achievement = 990000
            userPlayDate = Date(0)
            isClear = true
            isMissless = true
            isFullCombo = true
            isAllPerfect = true
        }),
        userStageUpList = emptyList()
    )

    private fun checkFieldNamesMatchRecursive(baselineNode: JsonNode, currentNode: JsonNode, path: String = "$") {
        if (baselineNode.isObject && currentNode.isObject) {
            val baselineKeys = baselineNode.fieldNames().asSequence().toSet()
            val currentKeys = currentNode.fieldNames().asSequence().toSet()

            val missingKeys = baselineKeys - currentKeys
            assertTrue(missingKeys.isEmpty(), "Missing keys at path '$path': $missingKeys. Current keys present: $currentKeys")

            for (key in baselineKeys) {
                checkFieldNamesMatchRecursive(baselineNode.get(key), currentNode.get(key), "$path.$key")
            }
        } else if (baselineNode.isArray && currentNode.isArray) {
            for (i in 0 until baselineNode.size()) {
                if (i < currentNode.size()) {
                    checkFieldNamesMatchRecursive(baselineNode.get(i), currentNode.get(i), "$path[$i]")
                }
            }
        }
    }

    @Test
    fun testObjectMapperWithoutKotlinModuleStripsIsGetters() {
        // Standard Jackson 2 ObjectMapper() without KotlinModule strips 'is' getters:
        val plainObjectMapper = ObjectMapper()
        val plainJson = plainObjectMapper.writeValueAsString(sampleMai2Export())
        val plainTree = plainObjectMapper.readTree(plainJson)
        val plainPlaylogNode = plainTree.at("/userPlaylogList/0")

        assertTrue(plainPlaylogNode.has("clear"), "Plain ObjectMapper has 'clear' instead of 'isClear'")
        assertTrue(plainPlaylogNode.has("fullCombo"), "Plain ObjectMapper has 'fullCombo' instead of 'isFullCombo'")
        assertTrue(plainPlaylogNode.has("allPerfect"), "Plain ObjectMapper has 'allPerfect' instead of 'isAllPerfect'")
        assertFalse(plainPlaylogNode.has("isClear"), "Plain ObjectMapper is missing 'isClear'")
    }

    @Test
    fun testMai2ExportFullTreeAgainstBaseline() {
        val currentTree = JACKSON.readTree(JACKSON.writeValueAsString(sampleMai2Export()))
        val baselineTree = JACKSON.readTree(loadResourceJson("/json/mai2_export_web.json"))
        checkFieldNamesMatchRecursive(baselineTree, currentTree)

        val playlogNode = currentTree.at("/userPlaylogList/0")
        assertTrue(playlogNode.has("isClear"))
        assertTrue(playlogNode.has("isFullCombo"))
        assertTrue(playlogNode.has("isAllPerfect"))
        assertTrue(playlogNode.has("isTap"))
        assertTrue(playlogNode.has("isHold"))
        assertTrue(playlogNode.has("isSlide"))
        assertTrue(playlogNode.has("isTouch"))
        assertTrue(playlogNode.has("isBreak"))
    }

    @Test
    fun testChu3ExportFullTreeAgainstBaseline() {
        val currentTree = JACKSON.readTree(JACKSON.writeValueAsString(sampleChu3Export()))
        val baselineTree = JACKSON.readTree(loadResourceJson("/json/chu3_export_web.json"))
        checkFieldNamesMatchRecursive(baselineTree, currentTree)

        val playlogNode = currentTree.at("/userPlaylogList/0")
        assertTrue(playlogNode.has("isClear"))
        assertTrue(playlogNode.has("isFullCombo"))
        assertTrue(playlogNode.has("isAllJustice"))
        assertTrue(playlogNode.has("isAllPerfect"))
    }

    @Test
    fun testOngekiExportFullTreeAgainstBaseline() {
        val currentTree = JACKSON.readTree(JACKSON.writeValueAsString(sampleOngekiExport()))
        val baselineTree = JACKSON.readTree(loadResourceJson("/json/ongeki_export_web.json"))
        checkFieldNamesMatchRecursive(baselineTree, currentTree)

        val playlogNode = currentTree.at("/userPlaylogList/0")
        assertTrue(playlogNode.has("isFullBell"))
        assertTrue(playlogNode.has("isFullCombo"))
        assertTrue(playlogNode.has("isAllBreak"))
        assertTrue(playlogNode.has("isAllPerfect"))
    }

    @Test
    fun testWaccaExportFullTreeAgainstBaseline() {
        val currentTree = JACKSON.readTree(JACKSON.writeValueAsString(sampleWaccaExport()))
        val baselineTree = JACKSON.readTree(loadResourceJson("/json/wacca_export_web.json"))
        checkFieldNamesMatchRecursive(baselineTree, currentTree)

        val playlogNode = currentTree.at("/userPlaylogList/0")
        assertTrue(playlogNode.has("isClear"))
        assertTrue(playlogNode.has("isMissless"))
        assertTrue(playlogNode.has("isFullCombo"))
        assertTrue(playlogNode.has("isAllPerfect"))
    }

    @Test
    fun testGameFacingMaimai2PlaylogFieldNamesAndValuesMatchBaselineRecursively() {
        val mai2Playlog = sampleMai2Playlog()

        val currentBasicTree = BASIC_MAPPER.readTree(BASIC_MAPPER.writeValueAsString(mai2Playlog))
        val baselineBasicTree = BASIC_MAPPER.readTree(loadResourceJson("/json/mai2_playlog_basic.json"))
        checkFieldNamesMatchRecursive(baselineBasicTree, currentBasicTree, "Mai2UserPlaylog (BASIC_MAPPER)")

        val currentStringTree = STRING_MAPPER.readTree(STRING_MAPPER.writeValueAsString(mai2Playlog))
        val baselineStringTree = STRING_MAPPER.readTree(loadResourceJson("/json/mai2_playlog_string.json"))
        checkFieldNamesMatchRecursive(baselineStringTree, currentStringTree, "Mai2UserPlaylog (STRING_MAPPER)")
    }

    @Test
    fun testJackson3JsonMapperWithKotlinModule() {
        val j3Mapper = JsonMapper.builder()
            .addModule(KotlinModule.Builder().build())
            .build()

        val j3Json = j3Mapper.writeValueAsString(sampleMai2Export())
        val j3Tree = j3Mapper.readTree(j3Json)
        val j3PlaylogNode = j3Tree.at("/userPlaylogList/0")

        assertTrue(j3PlaylogNode.has("isClear"), "Jackson 3 JsonMapper with KotlinModule must have 'isClear'")
        assertTrue(j3PlaylogNode.has("isFullCombo"), "Jackson 3 JsonMapper with KotlinModule must have 'isFullCombo'")
        assertTrue(j3PlaylogNode.has("isAllPerfect"), "Jackson 3 JsonMapper with KotlinModule must have 'isAllPerfect'")
    }
}
