package de.arxsilex.maptools.converter.worker

import crosby.binary.file.BlockOutputStream
import crosby.binary.osmosis.OsmosisSerializer
import de.arxsilex.maptools.converter.core.pbf.PbfBlockScanner
import de.arxsilex.maptools.converter.core.pbf.PbfDataBlockReader
import de.arxsilex.maptools.converter.core.pbf.PrimitiveBlockDecoder
import java.nio.file.Files
import java.nio.file.Path
import java.util.Date
import kotlin.io.path.createTempFile
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
import org.openstreetmap.osmosis.core.Osmosis
import org.openstreetmap.osmosis.core.container.v0_6.NodeContainer
import org.openstreetmap.osmosis.core.domain.v0_6.Node
import org.openstreetmap.osmosis.core.domain.v0_6.OsmUser

class CombineRequestTest {
    @Test
    fun `parses repeated inputs and creates sorted version resolving merge pipeline`() {
        val first = createTempFile(suffix = ".osm.pbf")
        val second = createTempFile(suffix = ".osm.pbf")
        val output = first.resolveSibling("combined-${System.nanoTime()}.osm.pbf")
        try {
            val request = CombineRequest.parse(
                arrayOf(
                    "combine",
                    "--input", first.toString(),
                    "--input", second.toString(),
                    "--pre-cropped-input", second.toString(),
                    "--output", output.toString(),
                    "--temp-dir", output.resolveSibling("session-temp").toString(),
                    "--west", "7", "--south", "49", "--east", "10", "--north", "52",
                ),
            )

            assertEquals(listOf(first, second), request.inputs)
            assertEquals(setOf(second), request.preCroppedInputs)
            assertEquals(output.resolveSibling("session-temp"), request.temporaryDirectory)
            val arguments = request.osmosisMergeArguments(request.inputs, output.resolveSibling("partial.pbf")).toList()
            assertEquals(2, arguments.count { it == "--sort" })
            assertEquals(1, arguments.count { it == "--merge" })
            assertTrue("conflictResolutionMethod=version" in arguments)
        } finally {
            Files.deleteIfExists(first)
            Files.deleteIfExists(second)
        }
    }

    @Test
    fun `osmosis pipeline deduplicates overlap and keeps newest entity version`() {
        val first = createTempFile(suffix = ".osm.pbf")
        val second = createTempFile(suffix = ".osm.pbf")
        val output = first.resolveSibling("merged-${System.nanoTime()}.osm.pbf")
        try {
            writeNodes(first, TestNode(1, 1, 50.0, 8.0))
            writeNodes(second, TestNode(1, 2, 50.1, 8.1), TestNode(2, 1, 50.2, 8.2))
            val request = CombineRequest(
                inputs = listOf(first, second),
                output = output,
                bounds = de.arxsilex.maptools.converter.core.GeoBounds(7.0, 49.0, 9.0, 51.0),
                cacheDirectory = output.resolveSibling("unused-cache"),
                temporaryDirectory = output.parent.resolve("temporary"),
            )

            Osmosis.run(request.osmosisMergeArguments(request.inputs, output))

            val nodes = mutableListOf<de.arxsilex.maptools.converter.core.pbf.OsmPreviewNode>()
            val index = PbfBlockScanner.scan(output)
            PbfDataBlockReader.open(output).use { blocks ->
                index.entries.filter { it.type == "OSMData" }.forEach { entry ->
                    PrimitiveBlockDecoder.decode(blocks.read(entry), onNode = nodes::add)
                }
            }
            assertEquals(listOf(1L, 2L), nodes.map { it.id })
            assertEquals(50.1, nodes.first().latitude, 0.000_000_001)
            assertEquals(8.1, nodes.first().longitude, 0.000_000_001)
        } finally {
            Files.deleteIfExists(first)
            Files.deleteIfExists(second)
            Files.deleteIfExists(output)
        }
    }

    @Suppress("DEPRECATION")
    private fun writeNodes(path: Path, vararg nodes: TestNode) {
        val serializer = OsmosisSerializer(BlockOutputStream(Files.newOutputStream(path)))
        try {
            serializer.setUseDense(true)
            serializer.initialize(emptyMap())
            nodes.forEach { node ->
                serializer.process(
                    NodeContainer(
                        Node(
                            node.id,
                            node.version,
                            Date(0),
                            OsmUser.NONE,
                            0,
                            node.latitude,
                            node.longitude,
                        ),
                    ),
                )
            }
            serializer.complete()
        } finally {
            serializer.close()
        }
    }

    private data class TestNode(
        val id: Long,
        val version: Int,
        val latitude: Double,
        val longitude: Double,
    )
}
