package com.github.nullptroma.wallenc.domain.vault.storages.common

import com.github.nullptroma.wallenc.domain.datatypes.StorageSyncJournal
import com.github.nullptroma.wallenc.domain.datatypes.StorageSyncJournalEntry
import com.github.nullptroma.wallenc.domain.datatypes.StorageSyncJournalMerge
import com.github.nullptroma.wallenc.domain.datatypes.StorageSyncRevision
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import java.util.UUID

/**
 * Буфер журнала sync: накопление записей и безопасный flush (без потери pending при ошибке записи).
 */
class StorageSyncJournalBuffer(
    private val syncActorId: String,
    private val originStorageUuid: UUID?,
    private val readJournal: suspend () -> StorageSyncJournal,
    private val writeJournal: suspend (StorageSyncJournal) -> Unit,
) {
    private val pendingMutex = Mutex()
    private val flushMutex = Mutex()
    private var pendingJournalEntries: StorageSyncJournal = emptyMap()

    @Volatile
    private var sequenceHighWatermark: Long? = null

    suspend fun flushPending() {
        flushMutex.withLock {
            flushPendingUnderLock()
        }
    }

    suspend fun putEntries(entries: StorageSyncJournal) {
        flushPending()
        if (entries.isEmpty()) {
            return
        }
        val current = readJournal()
        val merged = StorageSyncJournalMerge.merge(current, entries)
        if (merged == current) {
            return
        }
        writeJournal(merged)
        refreshSequenceHighWatermark(merged)
    }

    suspend fun appendEntry(path: String, entry: StorageSyncJournalEntry) {
        pendingMutex.withLock {
            pendingJournalEntries = pendingJournalEntries + (path to entry)
        }
        flushPending()
    }

    suspend fun nextSequence(): Long {
        flushPending()
        val diskMax = readJournal().values.maxOfOrNull { it.revision.sequence } ?: 0L
        val pendingMax = pendingMutex.withLock {
            pendingJournalEntries.values.maxOfOrNull { it.revision.sequence } ?: 0L
        }
        val base = maxOf(diskMax, pendingMax, sequenceHighWatermark ?: 0L)
        val next = base + 1L
        sequenceHighWatermark = next
        return next
    }

    fun buildEntry(
        path: String,
        operation: com.github.nullptroma.wallenc.domain.datatypes.StorageSyncOperation,
        sequence: Long,
    ): StorageSyncJournalEntry {
        return StorageSyncJournalEntry(
            path = path,
            operation = operation,
            revision = StorageSyncRevision(
                sequence = sequence,
                actorId = syncActorId,
                createdAt = java.time.Instant.now(),
            ),
            originStorageUuid = originStorageUuid,
        )
    }

    private suspend fun flushPendingUnderLock() {
        val pending = pendingMutex.withLock {
            if (pendingJournalEntries.isEmpty()) {
                return
            }
            val snapshot = pendingJournalEntries
            pendingJournalEntries = emptyMap()
            snapshot
        }
        try {
            val current = readJournal()
            val merged = StorageSyncJournalMerge.merge(current, pending)
            if (merged != current) {
                writeJournal(merged)
            }
            refreshSequenceHighWatermark(merged)
        } catch (e: Exception) {
            pendingMutex.withLock {
                pendingJournalEntries = StorageSyncJournalMerge.merge(pending, pendingJournalEntries)
            }
            throw e
        }
    }

    private fun refreshSequenceHighWatermark(journal: StorageSyncJournal) {
        val maxSeq = journal.values.maxOfOrNull { it.revision.sequence } ?: return
        val current = sequenceHighWatermark
        sequenceHighWatermark = if (current == null) maxSeq else maxOf(current, maxSeq)
    }
}
