package wang.yaojia.webterm.session

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 wang.yaojia.webterm.wire.TimelineEvent

/** Ports iOS `AwayDigestTests` — once-per-reconnect fold, all-zero suppressed. */
class AwayDigestTest {

    private fun event(at: Long, cls: String, label: String = cls): TimelineEvent =
        TimelineEvent(at = at, eventClass = cls, label = label)

    @Test
    fun emptyDigestReportsEmpty() {
        assertTrue(AwayDigest.EMPTY.isEmpty)
        assertEquals(0, AwayDigest.EMPTY.toolRuns)
        assertTrue(AwayDigest.EMPTY.recent.isEmpty())
    }

    @Test
    fun noEventsReducesToEmpty() {
        assertEquals(AwayDigest.EMPTY, AwayDigest.reduce(emptyList(), sinceMs = 100, limit = 5))
    }

    @Test
    fun eventsStrictlyBeforeSinceAreExcluded() {
        val events = listOf(event(at = 99, cls = "tool"))
        assertEquals(AwayDigest.EMPTY, AwayDigest.reduce(events, sinceMs = 100, limit = 5))
    }

    @Test
    fun anEventAtTheSinceInstantStillCounts() {
        val events = listOf(event(at = 100, cls = "tool"))
        val digest = AwayDigest.reduce(events, sinceMs = 100, limit = 5)
        assertEquals(1, digest.toolRuns)
        assertFalse(digest.isEmpty)
    }

    @Test
    fun countsAndFlagsAreFoldedPerClass() {
        val events = listOf(
            event(at = 100, cls = "tool"),
            event(at = 101, cls = "tool"),
            event(at = 102, cls = "waiting"),
            event(at = 103, cls = "done"),
            event(at = 104, cls = "stuck"),
            event(at = 105, cls = "user"),
        )
        val digest = AwayDigest.reduce(events, sinceMs = 100, limit = 10)
        assertEquals(2, digest.toolRuns)
        assertEquals(1, digest.waitingCount)
        assertTrue(digest.sawDone)
        assertTrue(digest.sawStuck)
    }

    @Test
    fun recentIsSortedOldestToNewestAndTruncatedToNewest() {
        // Deliberately out of order on input.
        val events = listOf(
            event(at = 300, cls = "tool", label = "c"),
            event(at = 100, cls = "tool", label = "a"),
            event(at = 200, cls = "tool", label = "b"),
        )
        val digest = AwayDigest.reduce(events, sinceMs = 0, limit = 2)
        // limit=2 keeps the two NEWEST, still oldest→newest ordered.
        assertEquals(listOf("b", "c"), digest.recent.map { it.label })
    }

    @Test
    fun nonPositiveLimitKeepsNoRecentEntries() {
        val events = listOf(event(at = 100, cls = "tool"))
        val digest = AwayDigest.reduce(events, sinceMs = 0, limit = 0)
        assertTrue(digest.recent.isEmpty())
        assertEquals(1, digest.toolRuns) // counts still fold
    }

    @Test
    fun onlyUserEventsWithNoRecentSuppressesTheDigest() {
        // away events exist but none are tool/waiting/done/stuck AND none kept →
        // equals EMPTY → the UI suppresses it (all-zero suppressed).
        val events = listOf(event(at = 100, cls = "user"), event(at = 101, cls = "user"))
        val digest = AwayDigest.reduce(events, sinceMs = 0, limit = 0)
        assertTrue(digest.isEmpty)
        assertEquals(AwayDigest.EMPTY, digest)
    }

    @Test
    fun userOnlyEventsWithRecentAreNotSuppressed() {
        val events = listOf(event(at = 100, cls = "user", label = "typed"))
        val digest = AwayDigest.reduce(events, sinceMs = 0, limit = 5)
        assertFalse(digest.isEmpty) // recent non-empty even though counters are zero
        assertEquals(listOf("typed"), digest.recent.map { it.label })
    }
}
