package net.sergeych.toread.storage.jdbc

import java.io.File
import java.security.MessageDigest

data class ManagedBookObject(
    val objectId: String,
    val relativePath: String,
    val sizeBytes: Long,
)

class ManagedBookObjectStore(
    private val libraryDirectory: File,
) {
    private val objectsDirectory = File(libraryDirectory, "objects")

    fun importFile(source: File): ManagedBookObject {
        require(source.isFile) { "Book source is not a file: ${source.path}" }
        objectsDirectory.mkdirs()

        val tempFile = File.createTempFile("book-object-", ".tmp", objectsDirectory)
        val digest = MessageDigest.getInstance("SHA-256")
        var size = 0L

        try {
            source.inputStream().use { input ->
                tempFile.outputStream().use { output ->
                    val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
                    while (true) {
                        val read = input.read(buffer)
                        if (read < 0) break
                        digest.update(buffer, 0, read)
                        output.write(buffer, 0, read)
                        size += read
                    }
                }
            }

            val objectId = digest.digest().toHex()
            val relativePath = relativeObjectPath(objectId)
            val target = File(libraryDirectory, relativePath)
            target.parentFile?.mkdirs()

            if (target.exists()) {
                tempFile.delete()
            } else if (!tempFile.renameTo(target)) {
                tempFile.copyTo(target, overwrite = false)
                tempFile.delete()
            }

            return ManagedBookObject(
                objectId = objectId,
                relativePath = relativePath,
                sizeBytes = size,
            )
        } catch (t: Throwable) {
            tempFile.delete()
            throw t
        }
    }

    fun fileForObject(objectId: String): File = File(libraryDirectory, relativeObjectPath(objectId))

    fun exists(objectId: String): Boolean = fileForObject(objectId).isFile

    private fun relativeObjectPath(objectId: String): String {
        val prefix = objectId.take(2)
        return "objects/$prefix/$objectId"
    }
}

private fun ByteArray.toHex(): String = joinToString(separator = "") { byte ->
    (byte.toInt() and 0xff).toString(16).padStart(2, '0')
}
