package com.aegir.demo

import android.content.Context
import java.io.File
import java.util.zip.CRC32
import java.util.zip.ZipInputStream

/**
 * Bundled venues ship as a single venue_map_<ID>.zip asset (instead of loose
 * folders) to stay under the APK's 65,535 zip-entry limit. On first use the
 * zip is extracted to filesDir/venues/venue_map_<ID>/ and the map is served
 * from there via file:// URLs.
 */
object VenueAssets {

    private const val PREFIX = "venue_map_"
    private const val ZIP_EXT = ".zip"
    private const val MARKER = ".extracted"

    @JvmStatic
    fun zipAssetName(venueId: String): String = "$PREFIX$venueId$ZIP_EXT"

    @JvmStatic
    fun hasZipAsset(context: Context, venueId: String): Boolean = try {
        context.assets.list("")?.contains(zipAssetName(venueId)) == true
    } catch (e: Exception) {
        false
    }

    @JvmStatic
    fun extractedDir(context: Context, venueId: String): File =
        File(File(context.filesDir, "venues"), "$PREFIX$venueId")

    /**
     * Fingerprint (uncompressed length + CRC32) of the packaged zip asset.
     * filesDir survives APK reinstalls, so a content-independent marker would
     * keep serving a stale extraction after the bundled venue zip is rebuilt
     * (e.g. reprocessed tiles). Keying the marker on this fingerprint forces a
     * re-extraction whenever the asset changes.
     */
    private fun assetFingerprint(context: Context, venueId: String): String {
        val crc = CRC32()
        var length = 0L
        context.assets.open(zipAssetName(venueId)).use { input ->
            val buf = ByteArray(64 * 1024)
            while (true) {
                val n = input.read(buf)
                if (n < 0) break
                crc.update(buf, 0, n)
                length += n
            }
        }
        return "$length:${crc.value}"
    }

    /**
     * Extracts the venue zip asset on first use; subsequent calls are no-ops
     * unless the packaged asset has changed (see {@link #assetFingerprint}).
     * Must be called off the main thread (large venues are several thousand files).
     */
    @JvmStatic
    fun ensureExtracted(context: Context, venueId: String): File {
        val dir = extractedDir(context, venueId)
        val marker = File(dir, MARKER)
        val fingerprint = assetFingerprint(context, venueId)
        if (marker.exists() && runCatching { marker.readText() }.getOrNull() == fingerprint) {
            return dir
        }
        dir.deleteRecursively()
        if (!dir.mkdirs()) {
            throw IllegalStateException("Could not create venue extraction dir: $dir")
        }
        val rootPath = dir.canonicalPath + File.separator
        context.assets.open(zipAssetName(venueId)).use { input ->
            ZipInputStream(input.buffered()).use { zin ->
                var entry = zin.nextEntry
                while (entry != null) {
                    val out = File(dir, entry.name)
                    if (!out.canonicalPath.startsWith(rootPath)) {
                        throw SecurityException("Zip entry escapes extraction dir: ${entry.name}")
                    }
                    if (entry.isDirectory) {
                        out.mkdirs()
                    } else {
                        out.parentFile?.mkdirs()
                        out.outputStream().use { zin.copyTo(it) }
                    }
                    zin.closeEntry()
                    entry = zin.nextEntry
                }
            }
        }
        marker.writeText(fingerprint)
        return dir
    }
}
