package com.correx.infrastructure.tools.filesystem

import com.correx.core.artifactstore.ArtifactStore
import com.correx.core.events.events.FileWrittenEvent
import com.correx.core.events.types.ArtifactId
import com.correx.core.events.types.SessionId
import com.correx.core.events.types.ToolInvocationId
import kotlinx.coroutines.runBlocking
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
import java.nio.file.Files

class FileMutationReverserTest {

    private class FakeStore : ArtifactStore {
        val blobs = linkedMapOf<String, ByteArray>()
        override suspend fun put(bytes: ByteArray): ArtifactId {
            val id = ArtifactId(bytes.toString(Charsets.UTF_8).hashCode().toString())
            blobs[id.value] = bytes
            return id
        }
        override suspend fun get(id: ArtifactId): ByteArray? = blobs[id.value]
        override suspend fun flushBefore(commit: suspend () -> Unit) = commit()
    }

    private fun event(path: String, preImageHash: String?, preExisted: Boolean) = FileWrittenEvent(
        invocationId = ToolInvocationId("i"),
        sessionId = SessionId("s"),
        path = path,
        preImageHash = preImageHash,
        postImageHash = null,
        preExisted = preExisted,
        timestampMs = 0L,
    )

    @Test
    fun `restores the pre-image and is idempotent`(): Unit = runBlocking {
        val dir = Files.createTempDirectory("rev").toRealPath()
        val target = dir.resolve("f.txt")
        Files.writeString(target, "OLD")
        val store = FakeStore()
        val hash = store.put("OLD".toByteArray()).value
        Files.writeString(target, "NEW")

        val reverser = FileMutationReverser(store, setOf(dir))
        assertEquals(RevertResult.Restored, reverser.revert(event(target.toString(), hash, preExisted = true)))
        assertEquals("OLD", Files.readString(target))
        // idempotent: reverting again is still a clean restore
        assertEquals(RevertResult.Restored, reverser.revert(event(target.toString(), hash, preExisted = true)))
        assertEquals("OLD", Files.readString(target))
    }

    @Test
    fun `deletes a file that did not previously exist and is idempotent`(): Unit = runBlocking {
        val dir = Files.createTempDirectory("rev-del").toRealPath()
        val target = dir.resolve("new.txt")
        Files.writeString(target, "X")
        val reverser = FileMutationReverser(FakeStore(), setOf(dir))

        assertEquals(RevertResult.Deleted, reverser.revert(event(target.toString(), null, preExisted = false)))
        assertFalse(Files.exists(target))
        assertEquals(RevertResult.Deleted, reverser.revert(event(target.toString(), null, preExisted = false)))
    }

    @Test
    fun `rejects when the pre-image blob is missing and leaves the file untouched`(): Unit = runBlocking {
        val dir = Files.createTempDirectory("rev-missing").toRealPath()
        val target = dir.resolve("f.txt")
        Files.writeString(target, "NEW")
        val reverser = FileMutationReverser(FakeStore(), setOf(dir))

        val res = reverser.revert(event(target.toString(), "no-such-hash", preExisted = true))
        assertTrue(res is RevertResult.Rejected)
        assertEquals("NEW", Files.readString(target))
    }

    @Test
    fun `rejects a target outside the allowed roots without modifying it`(): Unit = runBlocking {
        val dir = Files.createTempDirectory("rev-in").toRealPath()
        val outside = Files.createTempDirectory("rev-out").toRealPath()
        val target = outside.resolve("f.txt")
        Files.writeString(target, "KEEP")
        val reverser = FileMutationReverser(FakeStore(), setOf(dir))

        val res = reverser.revert(event(target.toString(), null, preExisted = false))
        assertTrue(res is RevertResult.Rejected)
        assertTrue(Files.exists(target))
    }
}
