package com.correx.infrastructure.artifactscas.evict

import com.correx.infrastructure.artifactscas.compact.LivenessScanner
import com.correx.infrastructure.artifactscas.config.CasConfig
import com.correx.infrastructure.artifactscas.index.ArtifactIndex
import com.correx.infrastructure.artifactscas.segment.SegmentWriter
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.nio.file.Files
import java.nio.file.Path
import kotlin.io.path.listDirectoryEntries

data class EvictionReport(
    val evictedSegmentIds: List<Long>,
    val bytesReclaimed: Long,
)

class Evictor(
    config: CasConfig,
    private val index: ArtifactIndex,
    private val liveness: LivenessScanner,
) {
    private val segmentsDir: Path = config.rootDir.resolve("segments")
    private val maxTotalBytes: Long = config.maxTotalBytes

    suspend fun evict(activeSegmentId: Long): EvictionReport =
        withContext(Dispatchers.IO) {
            if (!Files.exists(segmentsDir)) return@withContext empty()
            val sizes = listSegmentSizes()
            if (sizes.isEmpty()) return@withContext empty()
            var totalBytes = sizes.values.sum()
            if (totalBytes <= maxTotalBytes) return@withContext empty()

            val scan = liveness.scan(segmentsDir)
            val evicted = mutableListOf<Long>()
            var reclaimed = 0L
            val candidates = sizes.keys.sorted()
                .filter { it != activeSegmentId && (scan.perSegment[it]?.liveBytes ?: 0L) == 0L }
            for (id in candidates) {
                if (totalBytes <= maxTotalBytes) break
                val size = sizes.getValue(id)
                index.deleteEntriesIn(id)
                runCatching { Files.deleteIfExists(segmentsDir.resolve(SegmentWriter.segmentFileName(id))) }
                evicted.add(id)
                reclaimed += size
                totalBytes -= size
            }
            if (evicted.isEmpty()) empty() else EvictionReport(evicted, reclaimed)
        }

    private fun listSegmentSizes(): Map<Long, Long> =
        segmentsDir.listDirectoryEntries("*.seg")
            .mapNotNull { p ->
                val id = runCatching { p.fileName.toString().removeSuffix(".seg").toLong() }.getOrNull()
                    ?: return@mapNotNull null
                val size = runCatching { Files.size(p) }.getOrDefault(0L)
                id to size
            }.toMap()

    private fun empty(): EvictionReport = EvictionReport(emptyList(), 0L)
}
