package com.ponzischeme89.memby.data

import com.ponzischeme89.memby.data.model.GatewayEmbyHealth
import com.ponzischeme89.memby.ui.retryLabel
import com.ponzischeme89.memby.ui.secondsUntil
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Assert.assertSame
import org.junit.Test

class EmbyOutageTest {

    private val down = GatewayEmbyHealth(monitored = true, reachable = false, retrySeconds = 60)
    private val up = GatewayEmbyHealth(monitored = true, reachable = true, retrySeconds = 60)

    /**
     * With the probe switched off nothing updates `reachable`, so trusting it would mean a
     * permanent red bar on a server that is working perfectly well.
     */
    @Test
    fun `an unmonitored server never raises the bar`() {
        val unmonitored = GatewayEmbyHealth(monitored = false, reachable = false)
        assertNull(nextOutageState(unmonitored, existing = null, nowMillis = 0L))
    }

    @Test
    fun `a reachable server clears the bar`() {
        val existing = EmbyOutage(nextAttemptAtMillis = 60_000L, retryIntervalSeconds = 60)
        assertNull(nextOutageState(up, existing, nowMillis = 0L))
    }

    @Test
    fun `an outage arms a countdown from the retry interval`() {
        val outage = nextOutageState(down, existing = null, nowMillis = 5_000L)
        assertNotNull(outage)
        assertEquals(65_000L, outage!!.nextAttemptAtMillis)
        assertEquals(60, outage.retryIntervalSeconds)
    }

    /**
     * The status poll runs six times per retry. If each one re-armed the deadline the
     * counter would reset every ten seconds and never reach zero, which reads as an app
     * that is stuck rather than a server that is being retried.
     */
    @Test
    fun `a live countdown survives the polls in the middle of it`() {
        val armed = nextOutageState(down, existing = null, nowMillis = 0L)!!
        var current = armed
        listOf(10_000L, 20_000L, 30_000L, 40_000L, 50_000L).forEach { now ->
            current = nextOutageState(down, current, now)!!
        }
        assertSame(armed, current)
    }

    /** Once the attempt is due, the next one is a fresh interval away. */
    @Test
    fun `an expired countdown is re-armed`() {
        val armed = nextOutageState(down, existing = null, nowMillis = 0L)!!
        val next = nextOutageState(down, armed, nowMillis = 60_000L)!!
        assertEquals(120_000L, next.nextAttemptAtMillis)
    }

    /** A server too old to send an interval still has to produce a sane countdown. */
    @Test
    fun `a missing retry interval falls back to the shared default`() {
        val outage = nextOutageState(
            GatewayEmbyHealth(monitored = true, reachable = false),
            existing = null,
            nowMillis = 0L,
        )!!
        assertEquals(MaintenanceMonitor.DEFAULT_RETRY_SECONDS, outage.retryIntervalSeconds)
    }

    @Test
    fun `the countdown never runs negative`() {
        assertEquals(0, secondsUntil(deadlineMillis = 1_000L, nowMillis = 9_000L))
        assertEquals(9, secondsUntil(deadlineMillis = 9_000L, nowMillis = 0L))
        // A part-second still has time left in it, so it rounds up rather than to zero.
        assertEquals(1, secondsUntil(deadlineMillis = 400L, nowMillis = 0L))
    }

    @Test
    fun `the countdown says what is about to happen`() {
        assertEquals("Retrying now…", retryLabel(0))
        assertEquals("Retrying now…", retryLabel(-3))
        assertEquals("Retrying in 1s", retryLabel(1))
        assertEquals("Retrying in 42s", retryLabel(42))
    }
}
