package de.arxsilex.maptools.converter.desktop

import java.nio.ByteBuffer
import java.nio.file.Files
import java.sql.DriverManager
import de.arxsilex.maptools.converter.core.GeoBounds
import de.arxsilex.maptools.converter.core.ElevationOutputFormat
import de.arxsilex.maptools.converter.core.MapFormatVersion
import de.arxsilex.maptools.converter.core.PoiSchemaVersion
import kotlin.io.path.createTempFile
import kotlin.test.Test
import kotlin.test.assertFailsWith

class ExportOutputValidatorTest {
    @Test
    fun `validates ASCII elevation output`() {
        val directory = kotlin.io.path.createTempDirectory("validate-elevation-")
        try {
            val output = directory.resolve("terrain.asc")
            java.nio.file.Files.writeString(
                output,
                "ncols 2\nnrows 2\nxllcorner 7\nyllcorner 49\ncellsize 0.01\nNODATA_value -9999\n1 2\n3 4\n",
            )
            ExportOutputValidator.validateElevation(output, ElevationOutputFormat.ESRI_ASCII_GRID)
        } finally {
            directory.toFile().deleteRecursively()
        }
    }

    @Test
    fun `optional real mapsforge map and poi outputs pass structural validation`() {
        val map = System.getenv("MAPTOOLS_TEST_MAP_OUTPUT")?.takeIf(String::isNotBlank)?.let(java.nio.file.Path::of)
            ?: return
        val poi = System.getenv("MAPTOOLS_TEST_POI_OUTPUT")?.takeIf(String::isNotBlank)?.let(java.nio.file.Path::of)
            ?: return
        val coordinates = System.getenv("MAPTOOLS_TEST_OUTPUT_BOUNDS")
            ?.split(',')
            ?.map(String::toDouble)
            ?.takeIf { it.size == 4 }
            ?: error("MAPTOOLS_TEST_OUTPUT_BOUNDS muss west,south,east,north enthalten.")
        val bounds = GeoBounds(coordinates[0], coordinates[1], coordinates[2], coordinates[3])

        ExportOutputValidator.validateMapsforgeMap(map, bounds, MapFormatVersion.V5)
        ExportOutputValidator.validateMapsforgePoi(poi, PoiSchemaVersion.V4)
    }

    @Test
    fun `accepts mapsforge format version five`() {
        val file = createTempFile(suffix = ".map")
        try {
            val header = ByteBuffer.allocate(28)
                .put("mapsforge binary OSM".encodeToByteArray())
                .putInt(256)
                .putInt(5)
                .array()
            Files.write(file, header)
            ExportOutputValidator.validateMapsforgeMapHeader(file, MapFormatVersion.V5)
        } finally {
            Files.deleteIfExists(file)
        }
    }

    @Test
    fun `accepts mapsforge format version four when requested`() {
        val file = createTempFile(suffix = ".map")
        try {
            val header = ByteBuffer.allocate(28)
                .put("mapsforge binary OSM".encodeToByteArray())
                .putInt(256)
                .putInt(4)
                .array()
            Files.write(file, header)
            ExportOutputValidator.validateMapsforgeMapHeader(file, MapFormatVersion.V4)
        } finally {
            Files.deleteIfExists(file)
        }
    }

    @Test
    fun `rejects unknown mapsforge version`() {
        val file = createTempFile(suffix = ".map")
        try {
            val header = ByteBuffer.allocate(28)
                .put("mapsforge binary OSM".encodeToByteArray())
                .putInt(256)
                .putInt(99)
                .array()
            Files.write(file, header)
            assertFailsWith<IllegalArgumentException> {
                ExportOutputValidator.validateMapsforgeMapHeader(file, MapFormatVersion.V5)
            }
        } finally {
            Files.deleteIfExists(file)
        }
    }

    @Test
    fun `accepts mapsforge poi version four database`() {
        val file = createTempFile(suffix = ".poi")
        try {
            Files.delete(file)
            Class.forName("org.sqlite.JDBC")
            DriverManager.getConnection("jdbc:sqlite:${file.toAbsolutePath()}").use { connection ->
                connection.createStatement().use { statement ->
                    statement.execute("CREATE TABLE metadata (name TEXT, value TEXT)")
                    statement.execute("CREATE TABLE poi_categories (id INTEGER PRIMARY KEY, name TEXT, parent INTEGER)")
                    statement.execute("CREATE TABLE poi_category_map (id INTEGER, category INTEGER, PRIMARY KEY (id, category))")
                    statement.execute("CREATE TABLE poi_data (id INTEGER PRIMARY KEY, data TEXT)")
                    statement.execute("CREATE VIRTUAL TABLE poi_data_fts USING fts5(data, content='poi_data', content_rowid='id')")
                    statement.execute("CREATE VIRTUAL TABLE poi_index USING rtree(id, minLat, maxLat, minLon, maxLon)")
                    statement.execute("INSERT INTO metadata VALUES ('version', '4'), ('bounds', '50.0,8.0,51.0,9.0')")
                    statement.execute("INSERT INTO poi_data VALUES (1, 'name=Test')")
                    statement.execute("INSERT INTO poi_index VALUES (1, 50000000, 50000000, 8000000, 8000000)")
                    statement.execute("INSERT INTO poi_data_fts(poi_data_fts) VALUES ('rebuild')")
                }
            }
            ExportOutputValidator.validateMapsforgePoi(file, PoiSchemaVersion.V4)
        } finally {
            Files.deleteIfExists(file)
        }
    }

    @Test
    fun `rejects sqlite file without mapsforge poi schema`() {
        val file = createTempFile(suffix = ".poi")
        try {
            Files.delete(file)
            Class.forName("org.sqlite.JDBC")
            DriverManager.getConnection("jdbc:sqlite:${file.toAbsolutePath()}").use { connection ->
                connection.createStatement().use { statement ->
                    statement.execute("CREATE TABLE unrelated (id INTEGER PRIMARY KEY)")
                }
            }
            assertFailsWith<IllegalArgumentException> {
                ExportOutputValidator.validateMapsforgePoi(file, PoiSchemaVersion.V4)
            }
        } finally {
            Files.deleteIfExists(file)
        }
    }
}
