package com.correx.infrastructure.artifactscas.segment

import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.RandomAccessFile
import java.nio.file.Files
import java.nio.file.Path
import kotlin.io.path.listDirectoryEntries

data class Location(val segmentId: Long, val offset: Long, val length: Int)

class SegmentWriter(
    private val segmentsDir: Path,
    private val maxSegmentBytes: Long,
) : AutoCloseable {
    private var currentSegmentId: Long = 0
    private var currentRaf: RandomAccessFile? = null
    private var currentOffset: Long = 0

    init {
        Files.createDirectories(segmentsDir)
        openOrCreateActive()
    }

    suspend fun append(payload: ByteArray, hash: ByteArray): Location = withContext(Dispatchers.IO) {
        rotateIfNeeded(payload.size)
        val raf = currentRaf!!
        val offset = currentOffset
        val header = SegmentLayout.encodeHeader(payload, hash)
        raf.write(header)
        raf.write(payload)
        currentOffset += header.size + payload.size
        Location(currentSegmentId, offset, payload.size)
    }

    suspend fun fsync() = withContext(Dispatchers.IO) {
        currentRaf?.fd?.sync()
        Unit
    }

    override fun close() {
        currentRaf?.close()
        currentRaf = null
    }

    private fun openOrCreateActive() {
        val highest = segmentsDir.listDirectoryEntries("*.seg")
            .mapNotNull { runCatching { it.fileName.toString().removeSuffix(".seg").toLong() }.getOrNull() }
            .maxOrNull()
        currentSegmentId = highest ?: 1L
        val path = segmentsDir.resolve(segmentFileName(currentSegmentId))
        val raf = RandomAccessFile(path.toFile(), "rw")
        raf.seek(raf.length())
        currentRaf = raf
        currentOffset = raf.length()
    }

    private fun rotateIfNeeded(incomingPayloadBytes: Int) {
        val recordBytes = SegmentLayout.HEADER_SIZE.toLong() + incomingPayloadBytes
        if (currentOffset == 0L) return
        if (currentOffset + recordBytes <= maxSegmentBytes) return
        currentRaf?.fd?.sync()
        currentRaf?.close()
        currentSegmentId += 1
        val path = segmentsDir.resolve(segmentFileName(currentSegmentId))
        val raf = RandomAccessFile(path.toFile(), "rw")
        currentRaf = raf
        currentOffset = 0
    }

    companion object {
        fun segmentFileName(id: Long): String = "%08d.seg".format(id)
    }
}
