package com.fabledsword.minstrel.player

import com.fabledsword.minstrel.player.output.upnp.SoapFaultException
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Test
import java.io.IOException
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith

/**
 * Locks in the transport-command retry semantics behind the locked-phone
 * Sonos drop fix: a transient IO stall (WiFi power-save) must be retried, a
 * SOAP fault from a responding renderer must NOT be retried, and an
 * exhausted retry must rethrow rather than loop forever. Without this, a
 * single failed command falsely reverted Sonos playback to the phone.
 */
class RetryTransientIoTest {

    @Test
    fun `returns immediately on first success`() = runTest {
        var calls = 0
        val result = retryTransientIo(attempts = 3, backoffMs = 0L) {
            calls += 1
            "ok"
        }
        assertEquals("ok", result)
        assertEquals(1, calls)
    }

    @Test
    fun `retries transient IO failure then succeeds`() = runTest {
        var calls = 0
        val result = retryTransientIo(attempts = 3, backoffMs = 0L) {
            calls += 1
            if (calls < 2) throw IOException("connect timeout")
            "ok"
        }
        assertEquals("ok", result)
        assertEquals(2, calls)
    }

    @Test
    fun `rethrows IO failure after exhausting attempts`() = runTest {
        var calls = 0
        assertFailsWith<IOException> {
            retryTransientIo(attempts = 3, backoffMs = 0L) {
                calls += 1
                throw IOException("still asleep")
            }
        }
        assertEquals(3, calls)
    }

    @Test
    fun `does not retry a SOAP fault from a responding renderer`() = runTest {
        var calls = 0
        assertFailsWith<SoapFaultException> {
            retryTransientIo(attempts = 3, backoffMs = 0L) {
                calls += 1
                throw SoapFaultException("718", "Invalid InstanceID")
            }
        }
        assertEquals(1, calls)
    }
}
