package com.dowstats.service.integrations

import com.dowstats.configuration.StorageConfig
import com.dowstats.data.dto.integration.AvailableMods
import com.dowstats.data.entities.Mod
import com.dowstats.data.repositories.ModRepository
import com.dowstats.service.w40k.ModAttribPathService
import com.dowstats.service.w40k.ModParserService
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import org.springframework.util.FileSystemUtils
import org.springframework.web.multipart.MultipartFile
import java.io.File
import java.io.FileOutputStream
import java.io.InputStream
import java.net.URL
import java.nio.charset.StandardCharsets
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import java.nio.file.StandardCopyOption
import java.util.zip.*


@Service
class ModStorageIntegrationService(
    val modStorageConfig: StorageConfig,
    val modRepository: ModRepository,
    val modParserService: ModParserService,
    val storageConfig: StorageConfig,
) {

    val objectMapper = jacksonObjectMapper()
    val log = LoggerFactory.getLogger(ModStorageIntegrationService::class.java)

    fun requestAvailableMods() {

        log.info("Requesting available mods")
        val urlString = "http://89.108.83.108/dow_stats_client/dow_stats_balance_mod/mods.txt"
        val connection = URL(urlString).openConnection()
        connection.setRequestProperty("X-Key", modStorageConfig.key)
        val inputStream: InputStream = connection.getInputStream()
        val modsJson = String(inputStream.readAllBytes(), StandardCharsets.UTF_8)
        val availableMods: List<AvailableMods> = objectMapper.readValue(modsJson)

        val prevSavedMods = modRepository.findAll()
        val lastMods = availableMods.take(modStorageConfig.takeLast)

        val lastVersionsFromServer = if (lastMods.all { !it.isBeta }) {
            lastMods
        } else {
            lastMods + availableMods.filter { !it.isBeta }.take(1)
        }


        val newVersions =
            lastVersionsFromServer.filter { serverVersion -> !prevSavedMods.any { it.technicalName == serverVersion.technicalName } }
                .sortedBy { it.id }

        newVersions.map { toSave ->
            try{
                downloadAndExtractMod(toSave.technicalName, toSave.version)
                val savedMod = modRepository.save(Mod().also {
                    it.version = toSave.version
                    it.isBeta = toSave.isBeta
                    it.name = "Dowstats balance mod"
                    it.technicalName = toSave.technicalName
                })
                convertAllToLowercase(savedMod)
                modParserService.parseModFilesAndSaveToDb(savedMod)
            } catch (e: Exception) {
                log.error("Error while download and extract mod", e)
            }

        }
    }

    @Transactional
    fun deleteMod(modId: Long) {
        modRepository.clearModData(modId)
    }

    @Transactional
    fun deleteMods() {
        modRepository.findAll().forEach{
            modRepository.clearModData(it.id!!)
        }
    }

    @Transactional
    fun reloadMod(modId: Long) {
        val mod = modRepository.findById(modId).orElseThrow { IllegalArgumentException("Mod not found") }
        modRepository.clearModData(modId)
        checkSgaAndExtract(mod)
        convertAllToLowercase(mod)
        modParserService.parseModFilesAndSaveToDb(mod)
    }


    fun saveModFromRequest(file: MultipartFile?, name: String, techName: String, version: String) {

        val uploadingMod = modRepository.findByTechnicalNameAndVersion(techName, version)
        val savedMod = uploadingMod ?: modRepository.save(Mod().also {
            it.version = version
            it.isBeta = false
            it.name = name
            it.technicalName = techName
        })

        file?.let {
            val modDirectoryTo = "$name$version"
            val fileStream = file.bytes.inputStream()
            unzip(fileStream, modDirectoryTo)
            modParserService.parseModFilesAndSaveToDb(savedMod)
            log.info("${file.originalFilename} successfull uploaded. Name: $name, version: $version")
        }
    }

    private fun checkSgaAndExtract(mod: Mod){
        val modDirectory = "${storageConfig.modStorage.replace("/", File.separator)}${File.separator}${mod.technicalName}${mod.version}"
        File(modDirectory).listFiles()?.forEach { sgaFolder ->
            if(sgaFolder.isDirectory && sgaFolder.name.endsWith(".sga")){
                // Перемещаем иконки
                val iconFolder = sgaFolder.path + "${File.separator}Data${File.separator}art${File.separator}ui${File.separator}ingame"
                if(File(iconFolder).exists()){
                    val iconFolderDestination = iconFolder.replace(sgaFolder.name + File.separator, "")
                    Files.createDirectories(Paths.get(iconFolderDestination))
                    FileSystemUtils.copyRecursively(Paths.get(iconFolder),Paths.get(iconFolderDestination))
                }
                // Перемещаем атрибуты
                val attribFolder = sgaFolder.path + "${File.separator}Data${File.separator}attrib"
                if(File(attribFolder).exists()) {
                    val attribFolderDestination = attribFolder.replace(sgaFolder.name + File.separator, "")
                    Files.createDirectories(Paths.get(attribFolder))
                    FileSystemUtils.copyRecursively(Paths.get(attribFolder),Paths.get(attribFolderDestination))
                }
                // Удаляем остальное
                FileSystemUtils.deleteRecursively(sgaFolder)
            }
        }
    }

    private fun convertAllToLowercase(mod: Mod){
        val iconDirectory = "${storageConfig.modStorage.replace("/", File.separator)}${File.separator}${mod.technicalName}${mod.version}${File.separator}Data${File.separator}art${File.separator}ui${File.separator}ingame"
        File(iconDirectory).listFiles()?.forEach { raceIconDirectory ->
            raceIconDirectory.listFiles()?.forEach { iconFile ->
                if(iconFile.name != iconFile.name.lowercase()){
                    iconFile.renameTo(File( raceIconDirectory.path + File.separator + iconFile.name.lowercase()))
                }
            }
        }

        val attribDirectory = "${storageConfig.modStorage.replace("/", File.separator)}${File.separator}${mod.technicalName}${mod.version}${File.separator}Data${File.separator}attrib"
        File(attribDirectory).listFiles()?.forEach { attribFolder ->
            attribFolder.listFiles()?.forEach { attribFile ->
                if(attribFile.name != attribFile.name.lowercase()){
                    attribFile.renameTo(File( attribFolder.path + File.separator + attribFile.name.lowercase()))
                }
            }
        }
        val spbsDirectory = "${storageConfig.modStorage.replace("/", File.separator)}${File.separator}${mod.technicalName}${mod.version}${File.separator}Data${File.separator}attrib${File.separator}sbps${File.separator}races"
        File(spbsDirectory).listFiles()?.forEach { raceFolder ->
            raceFolder.listFiles()?.forEach { spbsFile ->
                if(spbsFile.name != spbsFile.name.lowercase()){
                    spbsFile.renameTo(File( raceFolder.path + File.separator + spbsFile.name.lowercase()))
                }
            }
        }
        val ebpsDirectory = "${storageConfig.modStorage.replace("/", File.separator)}${File.separator}${mod.technicalName}${mod.version}${File.separator}Data${File.separator}attrib${File.separator}ebps${File.separator}races"
        File(ebpsDirectory).listFiles()?.forEach { raceFolder ->
            raceFolder.listFiles()?.forEach { typeFolder ->
                typeFolder.listFiles()?.forEach { ebpsFile ->
                    if(ebpsFile.name != ebpsFile.name.lowercase()){
                        ebpsFile.renameTo(File( raceFolder.path + File.separator + ebpsFile.name.lowercase()))
                    }
                }
            }
        }
    }

    private fun downloadAndExtractMod(modTechName: String, version: String) {
        log.info("Downloading mod $modTechName")
        val urlString = "http://89.108.83.108/dow_stats_client/dow_stats_balance_mod/$modTechName.zip"
        val connection = URL(urlString).openConnection()
        connection.setRequestProperty("X-Key", modStorageConfig.key)
        val inputStream: InputStream = connection.getInputStream()
        val modDirectoryTo = "$modTechName$version"
        unzip(inputStream, modDirectoryTo)
        alignBmModFolder(modDirectoryTo, modTechName)
    }

    private fun unzip(stream: InputStream, modDirectoryTo: String) {
        val unzipPath = "${modStorageConfig.modStorage}${File.separator}$modDirectoryTo"
        log.info("Start unzip mod to $modDirectoryTo to '$unzipPath'")
        ZipInputStream(stream, Charsets.ISO_8859_1).use { zis ->
            var entry: ZipEntry?
            val buffer = ByteArray(1024)
            while ((zis.nextEntry.also { entry = it }) != null) {
                val newFile = File(("$unzipPath${File.separator}${entry!!.name}"))
                if (entry!!.isDirectory) {
                    newFile.mkdirs()
                } else {
                    File(newFile.getParent()).mkdirs()
                    FileOutputStream(newFile).use { fos ->
                        var length: Int
                        while ((zis.read(buffer).also { length = it }) > 0) {
                            fos.write(buffer, 0, length)
                        }
                    }
                }
            }
        }
        log.info("Mod $modDirectoryTo unzip complete")
    }

    private fun alignBmModFolder(modFolder: String, modName: String) {
        val unzipPath = "${modStorageConfig.modStorage}${File.separator}$modFolder"
        FileSystemUtils.copyRecursively(Path.of("$unzipPath${File.separator}$modName"), Path.of(unzipPath))
        FileSystemUtils.deleteRecursively(Path.of("$unzipPath${File.separator}$modName"))
        FileSystemUtils.deleteRecursively(Path.of("$unzipPath${File.separator}Engine"))
        FileSystemUtils.deleteRecursively(Path.of("$unzipPath${File.separator}$modName.module"))
    }
}