package de.arxsilex.maptools.converter.desktop

import de.arxsilex.maptools.converter.core.ContentSelection
import de.arxsilex.maptools.converter.core.InputFileInfo
import de.arxsilex.maptools.converter.core.InputFormat
import de.arxsilex.maptools.converter.core.MapFormatVersion
import de.arxsilex.maptools.converter.core.PoiSchemaVersion
import de.arxsilex.maptools.converter.core.mapsforge.MapsforgeMapSource
import java.nio.charset.StandardCharsets
import java.nio.file.Files
import java.nio.file.Path
import java.security.MessageDigest

internal data class PreviewDetailSource(val input: InputFileInfo, val mapPath: Path?, val poiPath: Path? = null)

internal object PreviewDetailCache {
    fun prepare(
        inputs: List<InputFileInfo>,
        strings: AppStrings = LocalizationCatalog.strings(AppLanguage.GERMAN),
        onStatus: (String) -> Unit = {},
        isCancelled: () -> Boolean = { false },
    ): List<PreviewDetailSource> {
        val cacheDirectory = PlatformDirectories.previewCacheDirectory().resolve("detail")
        Files.createDirectories(cacheDirectory)
        return inputs.mapIndexed { index, input ->
            if (input.format == InputFormat.MAPSFORGE_MAP) {
                PreviewDetailSource(input, Path.of(input.path))
            } else if (input.format == InputFormat.MAPSFORGE_POI) {
                PreviewDetailSource(input, mapPath = null, poiPath = Path.of(input.path))
            } else {
                require(input.format == InputFormat.OSM_PBF)
                val source = Path.of(input.path).toAbsolutePath().normalize()
                val stem = "${safeName(source)}-${identity(source)}"
                val mapTarget = cacheDirectory.resolve("$stem.map")
                val poiTarget = cacheDirectory.resolve("$stem.poi")
                val mapValid = Files.isRegularFile(mapTarget) && runCatching {
                    MapsforgeMapSource.readMetadata(mapTarget).bounds.intersects(requireNotNull(input.bounds))
                }.getOrDefault(false)
                val poiValid = Files.isRegularFile(poiTarget) && runCatching {
                    ExportOutputValidator.validateMapsforgePoi(poiTarget, PoiSchemaVersion.V4)
                }.isSuccess
                if (!mapValid || !poiValid) {
                    val storage = PlatformDirectories.previewStorageStatus(input.sizeBytes)
                    require(storage.hasEnoughSpace) {
                        strings(
                            "detail.storage_low",
                            formatFileSize(storage.requiredBytes),
                            formatFileSize(storage.usableBytes),
                            storage.directory,
                        )
                    }
                    if (!mapValid) Files.deleteIfExists(mapTarget)
                    if (!poiValid) Files.deleteIfExists(poiTarget)
                    onStatus(strings("detail.build_once", index + 1, inputs.size))
                    WorkerLauncher.mapsforge(
                        input = source,
                        mapOutput = mapTarget.takeUnless { mapValid },
                        poiOutput = poiTarget.takeUnless { poiValid },
                        bounds = requireNotNull(input.bounds),
                        content = ContentSelection(),
                        mapFormatVersion = MapFormatVersion.V5,
                        poiSchemaVersion = PoiSchemaVersion.V4,
                        onLogLine = { line ->
                            if (line.contains("status=running")) {
                                onStatus(strings("detail.building", index + 1, inputs.size))
                            }
                        },
                        isCancelled = isCancelled,
                    )
                }
                PreviewDetailSource(input, mapTarget, poiTarget)
            }
        }
    }

    private fun safeName(source: Path): String = source.fileName.toString()
        .replace(Regex("[^A-Za-z0-9._-]"), "_")
        .take(72)

    private fun identity(source: Path): String {
        val value = "${source}|${Files.size(source)}|${Files.getLastModifiedTime(source).toMillis()}"
        return MessageDigest.getInstance("SHA-256")
            .digest(value.toByteArray(StandardCharsets.UTF_8))
            .take(10)
            .joinToString("") { "%02x".format(it) }
    }
}
