package com.dowstats.service.w40k

import com.dowstats.data.entities.*
import com.dowstats.data.entities.ability.Ability
import com.dowstats.data.entities.ability.AbilityEnvironment
import com.dowstats.data.entities.building.BuildingWeapon
import com.dowstats.data.entities.building.BuildingWeaponKey
import com.dowstats.data.entities.research.Research
import com.dowstats.data.entities.sergant.SergeantWeapon
import com.dowstats.data.entities.sergant.SergeantWeaponKey
import com.dowstats.data.entities.unit.UnitWeapon
import com.dowstats.data.entities.unit.UnitWeaponKey
import com.dowstats.data.entities.weapon.Weapon
import com.dowstats.data.repositories.*
import com.dowstats.data.rgd.RgdData
import com.dowstats.service.postparsing.PostParsingService
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service
import java.lang.Exception
import jakarta.transaction.Transactional
import org.slf4j.LoggerFactory
import java.io.File
import java.nio.file.Files
import kotlin.io.path.Path
import kotlin.io.path.name

@Service
class ModParserService @Autowired constructor(
    private val rgdParserService: RgdParserService,
    private val unitRgdExtractService: UnitRgdExtractService,
    private val researchRgdExtractService: ResearchRgdExtractService,
    private val raceRepository: RaceRepository,
    private val armorTypeRepository: ArmorTypeRepository,
    private val areaEffectRepository: AreaEffectRepository,
    private val areaFilterRepository: AreaFilterRepository,
    private val activationRepository: ActivationRepository,
    private val unitRepository: UnitRepository,
    private val sergeantRepository: SergeantRepository,
    private val abilityRgdExtractService: AbilityRgdExtractService,
    private val abilityEnvironmentRgdExtractService: AbilityEnvironmentRgdExtractService,
    private val abilityEnvironmentRepository: AbilityEnvironmentRepository,
    private val abilityRepository: AbilityRepository,
    private val weaponRgdExtractService: WeaponRgdExtractService,
    private val weaponRepository: WeaponRepository,
    private val modAttribPathService: ModAttribPathService,
    private val buildingRepository: BuildingRepository,
    private val buildingExtractService: BuildingRgdExtractService,
    private val postParsingService: PostParsingService,
    private val researchRepository: ResearchRepository,
    private val modRepository: ModRepository,
) {

    val defaultDictionary: MutableMap<Int, String> = mutableMapOf()

    init {
        RgdParserService::class.java.getClassLoader().getResourceAsStream("DXP2.ucs").bufferedReader(Charsets.UTF_8)
            .lines().forEach {
            val kv = it.split("\\s+".toRegex())
            if (kv.size < 2) return@forEach
            val key = kv.first().filter { it.isDigit() }.toInt()
            val value = kv.drop(1).joinToString(" ").replace("\u0000", "")
            defaultDictionary[key] = value
        }

        RgdParserService::class.java.getClassLoader().getResourceAsStream("W40k.ucs").bufferedReader(Charsets.UTF_8)
            .lines().forEach {
            val kv = it.split("\\s+".toRegex())
            if (kv.size < 2) return@forEach
            val key = kv.first().filter { it.isDigit() }.toInt()
            val value = kv.drop(1).joinToString(" ").replace("\u0000", "")
            defaultDictionary[key] = value
        }

        RgdParserService::class.java.getClassLoader().getResourceAsStream("Engine.ucs").bufferedReader(Charsets.UTF_8)
            .lines().forEach {
                val kv = it.split("\\s+".toRegex())
                if (kv.size < 2) return@forEach
                val key = kv.first().filter { it.isDigit() }.toInt()
                val value = kv.drop(1).joinToString(" ").replace("\u0000", "")
                defaultDictionary[key] = value
            }
    }

    val log = LoggerFactory.getLogger(ModParserService::class.java)

    @Transactional
    fun parseModFilesAndSaveToDb(mod: Mod) {

        log.info("Start parse mod files ${mod.technicalName}:${mod.version}")
        modRepository.clearModData(mod.id!!)

        val modFolderData = modAttribPathService.getModFolderData(mod.technicalName!!, mod.version!!)
        val racesList = Files.walk(Path(modAttribPathService.getSbpsAttribsFolderPath(modFolderData)), 1)
            .toList()
            .drop(1)
            .filter { Files.isDirectory(it) }.map { it.name }.toList()

        val armorTypes = if(mod.technicalName?.contains("UltimateApocalypse") == true)
            armorTypeRepository.findByIsUa(true).toSet()
        else armorTypeRepository.findByIsClassic(true).toSet()

        val areaEffects = areaEffectRepository.findAll().toSet()
        val areaFilters = areaFilterRepository.findAll().toSet()
        val activations = activationRepository.findAll().toSet()

        racesList.forEach {
            unitRepository.deleteAllByModIdAndRaceId(mod.id!!, it)
        }

        val modDictionary: Map<Int, String> = getModDictionary(mod, modFolderData)

        log.info("Extract dictionaries from $modFolderData")

        val enrichedModDictionary = defaultDictionary + modDictionary

        val weapons = saveWeapons(modFolderData, mod, armorTypes, enrichedModDictionary)
        val abilities = saveAbilities(modFolderData, mod, activations, areaEffects, areaFilters, armorTypes, enrichedModDictionary)

        val researches = saveResearches(modFolderData, mod, enrichedModDictionary)
        saveUnits(modFolderData, weapons, abilities, racesList, armorTypes, mod, enrichedModDictionary)
        saveBuildings(modFolderData, weapons, abilities, researches, racesList, armorTypes, mod, enrichedModDictionary)

        saveAbilityEnvironments(modFolderData, mod, abilities, armorTypes, enrichedModDictionary)

        modRepository.removeCampaignEntities(mod.id!!)
        postParsingService.bindResearch(mod)
        log.info("Complete parse mod ${mod.technicalName}:${mod.version}")


    }

    private fun getModDictionary(mod: Mod, modFolderData: String): Map<Int, String> {
        val folder =
            if (mod.technicalName == "multidungeon_rightpocalypse") modAttribPathService.getUcsFolderRus(modFolderData) else modAttribPathService.getUcsFolder(
                modFolderData
            )

        val modDictionary = mutableMapOf<Int, String>()

        File(folder).listFiles()?.forEach {
            it.bufferedReader(Charsets.UTF_16).lines().forEach {
                val kv = it.split("\\s+".toRegex())
                if (kv.size < 2) return@forEach
                val key = try {
                    kv.first().filter { it.isDigit() }.toInt()
                } catch (e: Exception) {
                    log.warn("Cant' get key ${kv.first()} for dict")
                    0
                }
                val value = kv.drop(1).joinToString(" ").replace("\u0000", "")
                modDictionary[key] = value
            }
        }

        return modDictionary
    }


    private fun saveResearches(
        modFolderData: String,
        mod: Mod,
        modDictionary: Map<Int, String>
    ): Set<Research> {

        val classicRgdDataResearches =
            rgdParserService.parseFolderToRgdFiles(
                modAttribPathService.getResearchAttribsPath(
                    modAttribPathService.pathToWanilaData,
                )
            )
        val modRgdDataResearches =
            rgdParserService.parseFolderToRgdFiles(
                modAttribPathService.getResearchAttribsPath(
                    modFolderData,
                )
            )

        val researchesFull = classicRgdDataResearches + modRgdDataResearches

        val researches = researchesFull.mapNotNull { researchData ->
            try {
                researchRgdExtractService.extractToResearchEntity(
                    researchData.key,
                    modDictionary,
                    researchData.value,
                    modFolderData,
                    mod,
                )
            } catch (e: Exception) {
                log.error("Can't extract ${researchData.key}", e)
                null
            }
        }
        return try {
            researchRepository.saveAll(researches).toSet()
        } catch (e: Exception) {
            throw e
        }
    }

    private fun saveUnits(
        modFolderData: String,
        weapons: Set<Weapon>,
        abilities: Set<Ability>,
        racesList: List<String>,
        armorTypes: Set<ArmorType>,
        mod: Mod,
        modDictionary: Map<Int, String>
    ) {

        val races = raceRepository.findAll().toList()

        racesList.forEach { raceFolder ->

            val classicRgdDataSquads =
                rgdParserService.parseFolderToRgdFiles(
                    modAttribPathService.getSbpsAttribsPath(
                        modAttribPathService.pathToWanilaData,
                        raceFolder
                    )
                )
            val modRgdDataSquads =
                rgdParserService.parseFolderToRgdFiles(
                    modAttribPathService.getSbpsAttribsPath(
                        modFolderData,
                        raceFolder
                    )
                )

            val classicRgdDataUnits =
                rgdParserService.parseFolderToRgdFiles(
                    modAttribPathService.getEbpsTroopsAttribsPath(
                        modAttribPathService.pathToWanilaData,
                        raceFolder
                    )
                )
            val modRgdDataUnits =
                rgdParserService.parseFolderToRgdFiles(
                    modAttribPathService.getEbpsTroopsAttribsPath(
                        modFolderData,
                        raceFolder
                    )
                )

            val modSquadsFull = classicRgdDataSquads + modRgdDataSquads
            val modUnitsFull = classicRgdDataUnits + modRgdDataUnits

            modSquadsFull.forEach { squadRgdData ->

                val baseUnitName = unitRgdExtractService.getUnitRgdFileNameFromSquadData(squadRgdData.value)
                    ?: throw Exception("Can't extract unit name from squad ${squadRgdData.key}")
                log.info("Start extracting $raceFolder: $baseUnitName")
                val unitRgdData: List<RgdData> =
                    modUnitsFull[baseUnitName] ?: emptyList()

                if (unitRgdData.isEmpty()) {
                    log.warn("Can't find rgd data for unit $baseUnitName")
                    return@forEach
                }

                val unitDataToSave =
                    try {
                        unitRgdExtractService.extractToUnitEntity(
                            squadRgdData.key,
                            baseUnitName,
                            modDictionary,
                            squadRgdData.value,
                            unitRgdData,
                            weapons,
                            abilities,
                            raceFolder,
                            modFolderData,
                            mod,
                            races,
                            armorTypes,
                            modUnitsFull
                        )
                    } catch (e: Exception) {
                        log.error("Can't extract $baseUnitName", e)
                        return@forEach
                    }

                try {
                    val unit = unitRepository.save(unitDataToSave.unit)
                    unit.weapons = unitDataToSave.unitWeapons?.map { weapon ->
                        UnitWeapon().also {
                            it.unit = unit
                            it.weapon = weapon.weapon
                            it.unitWeaponKey = UnitWeaponKey().also {
                                it.unitId = unit.id
                                it.weaponId = weapon.weapon?.id
                                it.hardpoint = weapon.hardpoint
                                it.hardpointOrder = weapon.hardpointOrder
                            }
                        }
                    }?.toMutableSet()
                    unitRepository.save(unit)


                    unitDataToSave.sergeants?.forEach { sergeantToSave ->
                        val sergeant = sergeantRepository.save(sergeantToSave.first.also {
                            it.unit = unit
                        })
                        sergeant.weapons = sergeantToSave.second.flatMap { weapon ->
                            sergeant.weaponHardpoints
                                .filter { it.weaponId == weapon.id }
                                .map {  hardpointOrder ->
                                SergeantWeapon().also {
                                    it.sergeant = sergeant
                                    it.weapon = weapon

                                    it.sergeantWeaponKey = SergeantWeaponKey().also { swk ->
                                        swk.sergeantId = sergeant.id
                                        swk.weaponId = weapon.id
                                        swk.hardpoint = hardpointOrder.hardpoint
                                        swk.hardpointOrder = hardpointOrder.order
                                    }
                                }
                            }
                        }.toMutableSet()
                        sergeantRepository.save(sergeant)
                    }
                } catch (e: Exception) {
                    log.error("Cant save unit ${unitDataToSave.unit.name}", e)
                    throw e
                }
            }
        }
    }

    private fun saveBuildings(
        modFolderData: String,
        weapons: Set<Weapon>,
        abilities: Set<Ability>,
        researches: Set<Research>,
        racesList: List<String>,
        armorTypes: Set<ArmorType>,
        mod: Mod, modDictionary: Map<Int, String>
    ) {

        val races = raceRepository.findAll().toList()

        val addonsRgdData = getAddonsRgdData(modFolderData)

        racesList.forEach { raceFolder ->
            val classicRgdDataStructures =
                rgdParserService.parseFolderToRgdFiles(
                    modAttribPathService.geBuildingAttribsPath(
                        modAttribPathService.pathToWanilaData,
                        raceFolder
                    )
                )
            val modRgdDataStructures =
                rgdParserService.parseFolderToRgdFiles(
                    modAttribPathService.geBuildingAttribsPath(
                        modFolderData,
                        raceFolder
                    )
                )

            val modStructuresFull = classicRgdDataStructures + modRgdDataStructures

            if(raceFolder == "npc") return@forEach
            val race = races.find { it.id == raceFolder }
            if(race == null){
                log.warn("Can't find race $raceFolder")
                return@forEach
            }
            val raceUnits = unitRepository.findByModIdAndRace(mod.id!!, race)

            modStructuresFull.forEach { structure ->
                val structureDataToSave =
                    try {
                        buildingExtractService.extractToBuildingEntity(
                            structure.key,
                            modDictionary,
                            structure.value,
                            weapons,
                            abilities,
                            researches,
                            raceUnits,
                            race,
                            modFolderData,
                            mod,
                            armorTypes,
                            addonsRgdData,
                        )
                    } catch (e: Exception) {
                        log.error("Can't extract ${structure.key}", e)
                        return@forEach
                    }

                try {
                    val building = buildingRepository.save(structureDataToSave.building)
                    building.weapons = structureDataToSave.buildingWeapons.flatMap { weapon ->
                        building.weaponHardpoints
                            .filter { it.weaponId == weapon.id }
                            .map { hardpointOrder ->
                                BuildingWeapon().also {
                                    it.building = building
                                    it.weapon = weapon
                                    it.buildingWeaponKey = BuildingWeaponKey().also {
                                        it.buildingId = building.id
                                        it.weaponId = weapon.id
                                        it.hardpoint = hardpointOrder.hardpoint
                                        it.hardpointOrder = hardpointOrder.order
                                    }
                                }
                            }
                    }.toMutableSet()
                    buildingRepository.save(building)

                } catch (e: Exception) {
                    throw e
                }
            }


        }
    }

    private fun getAddonsRgdData(modFolderData: String): Map<String, List<RgdData>> {

        val classicAddons =
            rgdParserService.parseFolderToRgdFiles("${modAttribPathService.pathToWanilaData}${File.separator}attrib${File.separator}addons")

        val modAddons =
            rgdParserService.parseFolderToRgdFiles(modAttribPathService.getAddonAttribsPath(modFolderData))

        return classicAddons + modAddons
    }

    private fun saveWeapons(modFolderData: String, mod: Mod, armorTypes: Set<ArmorType>, modDictionary: Map<Int, String>): Set<Weapon> {

        val classicWeapons =
            rgdParserService.parseFolderToRgdFiles("${modAttribPathService.pathToWanilaData}${File.separator}attrib${File.separator}weapon")

        val modWeapons =
            rgdParserService.parseFolderToRgdFiles(modAttribPathService.getWeaponAttribsPath(modFolderData))

        val allWeapons = classicWeapons + modWeapons

        val weaponsToSave = allWeapons.mapNotNull {
            weaponRgdExtractService.extractToWeaponEntity(it.key, it.value, mod, armorTypes, modFolderData, modDictionary)
        }

        return try {
            weaponRepository.saveAll(weaponsToSave).toSet()
        } catch (e: Exception) {
            throw e
        }
    }

    private fun saveAbilities(modFolderData: String,
                              mod: Mod,
                              activations: Set<Activation>,
                              areaEffects: Set<AreaEffect>,
                              areaFilters: Set<AreaFilter>,
                              armorTypes: Set<ArmorType>, modDictionary: Map<Int, String>): Set<Ability> {

        val classicAbilities =
            rgdParserService.parseFolderToRgdFiles("${modAttribPathService.pathToWanilaData}${File.separator}attrib${File.separator}abilities")

        val modAbilities =
            rgdParserService.parseFolderToRgdFiles(modAttribPathService.getAbilitiesAttribsPath(modFolderData))

        val allAbilities = classicAbilities + modAbilities

        val abilitiesToSave = allAbilities.mapNotNull {
            abilityRgdExtractService.extractToAbilityEntity(it.key, it.value, mod,
                activations,
                areaEffects,
                areaFilters,
                armorTypes,
                modFolderData,
                modDictionary
            )
        }

        return try {
            abilityRepository.saveAll(abilitiesToSave).toSet()
        } catch (e: Exception) {
            throw e
        }
    }

    private fun saveAbilityEnvironments(modFolderData: String,
                              mod: Mod,
                              abilities: Set<Ability>,
                              armorTypes: Set<ArmorType>,
                              modDictionary: Map<Int, String>): Set<AbilityEnvironment> {

        val classicAbilityEnvironments =
            rgdParserService.parseFolderToRgdFiles("${modAttribPathService.pathToWanilaData}${File.separator}attrib${File.separator}ebps${File.separator}environment${File.separator}gameplay")

        val modAbilityEnvironments =
            rgdParserService.parseFolderToRgdFiles(modAttribPathService.getAbilityEnvironmentsAttribsPath(modFolderData))

        val allAbilityEnvironments = classicAbilityEnvironments + modAbilityEnvironments

        val abilityEnvironmentsToSave = allAbilityEnvironments.mapNotNull {
            abilityEnvironmentRgdExtractService.extractToAbilityEnvironmentEntity(it.key, it.value, mod,
                modFolderData,
                abilities,
                armorTypes,
                modDictionary,
            )
        }

        return try {
            abilityEnvironmentRepository.saveAll(abilityEnvironmentsToSave).toSet()
        } catch (e: Exception) {
            throw e
        }
    }

}
