package com.correx.apps.server.personalization

import com.correx.core.config.OperatorProfile
import com.correx.core.events.events.EventMetadata
import com.correx.core.events.events.NewEvent
import com.correx.core.events.events.SteeringNoteAddedEvent
import com.correx.core.events.types.EventId
import com.correx.core.events.types.SessionId
import com.correx.core.journal.DecisionJournalProjector
import com.correx.core.journal.DefaultDecisionJournalReducer
import com.correx.core.journal.DefaultDecisionJournalRepository
import com.correx.core.sessions.projections.replay.DefaultEventReplayer
import com.correx.infrastructure.persistence.InMemoryEventStore
import kotlinx.coroutines.runBlocking
import kotlinx.datetime.Clock
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.io.TempDir
import java.nio.file.Files
import java.nio.file.Path
import java.util.UUID

class ProfileAdaptationServiceTest {

    private fun buildRepository(eventStore: InMemoryEventStore): DefaultDecisionJournalRepository =
        DefaultDecisionJournalRepository(
            DefaultEventReplayer(
                eventStore,
                DecisionJournalProjector(DefaultDecisionJournalReducer()),
            ),
        )

    private fun sessionId() = SessionId(UUID.randomUUID().toString())

    /** Seed a steering event so the journal has at least one relevant record. */
    private suspend fun seedSteeringEvent(eventStore: InMemoryEventStore, sessionId: SessionId) {
        eventStore.append(
            NewEvent(
                metadata = EventMetadata(
                    eventId = EventId(UUID.randomUUID().toString()),
                    sessionId = sessionId,
                    timestamp = Clock.System.now(),
                    schemaVersion = 1,
                    causationId = null,
                    correlationId = null,
                ),
                payload = SteeringNoteAddedEvent(
                    sessionId = sessionId,
                    content = "Use Kotlin idiomatic style",
                ),
            ),
        )
    }

    @Test
    fun `no relevant records — proposeAdaptation returns early, no file written`(@TempDir tempDir: Path): Unit =
        runBlocking {
            val eventStore = InMemoryEventStore()
            val repository = buildRepository(eventStore)
            val proposedPath = tempDir.resolve("profile.proposed.toml")

            val service = ProfileAdaptationService(
                decisionJournalRepository = repository,
                summarize = { "about = \"updated\"" },
                proposedPath = proposedPath,
            )

            service.proposeAdaptation(sessionId(), OperatorProfile())

            assertFalse(Files.exists(proposedPath), "No file should be written when there are no relevant records")
        }

    @Test
    fun `relevant records but summarize returns no changes — no file written`(@TempDir tempDir: Path): Unit =
        runBlocking {
            val eventStore = InMemoryEventStore()
            val sessionId = sessionId()
            seedSteeringEvent(eventStore, sessionId)
            val repository = buildRepository(eventStore)
            val proposedPath = tempDir.resolve("profile.proposed.toml")

            val service = ProfileAdaptationService(
                decisionJournalRepository = repository,
                summarize = { "# no changes\n# nothing to update" },
                proposedPath = proposedPath,
            )

            service.proposeAdaptation(sessionId, OperatorProfile())

            assertFalse(Files.exists(proposedPath), "No file should be written when summarize returns '# no changes'")
        }

    @Test
    fun `relevant records and real delta — file written at proposedPath`(@TempDir tempDir: Path): Unit =
        runBlocking {
            val eventStore = InMemoryEventStore()
            val sessionId = sessionId()
            seedSteeringEvent(eventStore, sessionId)
            val repository = buildRepository(eventStore)
            val proposedPath = tempDir.resolve("profile.proposed.toml")
            val deltaContent = "about = \"prefers Kotlin idiomatic style\""

            val service = ProfileAdaptationService(
                decisionJournalRepository = repository,
                summarize = { deltaContent },
                proposedPath = proposedPath,
            )

            service.proposeAdaptation(sessionId, OperatorProfile())

            assertTrue(Files.exists(proposedPath), "Proposed file should be written when a real delta is returned")
            assertTrue(
                Files.readString(proposedPath).contains("prefers Kotlin idiomatic style"),
                "Proposed file should contain the delta content",
            )
        }
}
