/*
 * SPDX-License-Identifier: GPL-3.0-or-later
 */
package com.runicgateway.app.ui

import com.runicgateway.app.core.result.ApiResult
import com.runicgateway.app.ui.components.isRetryable
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
import java.io.IOException

/** Unit tests for folding an [ApiResult] into a screen [UiState] (PLAN.md §7). */
class UiStateTest {

    @Test fun okBecomesSuccess() {
        assertEquals(UiState.Success("hi"), ApiResult.Ok("hi").toUiState())
    }

    @Test fun networkErrorMapsToNetworkKind() {
        val state = ApiResult.NetworkError(IOException()).toUiState()
        assertEquals(ErrorKind.NETWORK, (state as UiState.Error).kind)
    }

    @Test fun knownStatusesMapToKinds() {
        assertEquals(ErrorKind.NOT_FOUND, kindOf(404))
        assertEquals(ErrorKind.RATE_LIMITED, kindOf(429))
        assertEquals(ErrorKind.SHARD_OFFLINE, kindOf(503))
        assertEquals(ErrorKind.SERVER, kindOf(500))
    }

    @Test fun httpStatusIsPreserved() {
        val state = ApiResult.HttpError(503).toUiState() as UiState.Error
        assertEquals(503, state.httpStatus)
        assertTrue(ApiResult.HttpError(503).let { it.status == 503 })
    }

    // ── Shard reads: 404/403 mean "this shard doesn't publish it" (M11) ──

    @Test fun shardReadsTreat404And403AsFeatureUnavailable() {
        // requireFeature answers 404 for a disabled feature (deliberately not
        // disclosing that it exists) and 403 for a viewer below its audience rung.
        assertEquals(ErrorKind.FEATURE_UNAVAILABLE, shardKindOf(404))
        assertEquals(ErrorKind.FEATURE_UNAVAILABLE, shardKindOf(403))
    }

    @Test fun shardReadsLeaveEveryOtherStatusAlone() {
        assertEquals(ErrorKind.SHARD_OFFLINE, shardKindOf(503))
        assertEquals(ErrorKind.RATE_LIMITED, shardKindOf(429))
        assertEquals(ErrorKind.SERVER, shardKindOf(500))
        assertEquals(
            ErrorKind.NETWORK,
            (ApiResult.NetworkError(IOException()).toShardUiState() as UiState.Error).kind,
        )
        assertEquals(UiState.Success("hi"), ApiResult.Ok("hi").toShardUiState())
    }

    @Test fun nonShardReadsKeep404AsNotFound() {
        // The remap is scoped to shard routes on purpose: off them, a 404 is still a
        // deleted post or an unknown wiki slug.
        assertEquals(ErrorKind.NOT_FOUND, kindOf(404))
    }

    @Test fun anUnavailableFeatureIsNotRetryable() {
        // An admin controls this, so a retry button would read as a transient failure
        // the user could wait out.
        assertFalse(isRetryable(ErrorKind.FEATURE_UNAVAILABLE))
        for (kind in ErrorKind.entries.filter { it != ErrorKind.FEATURE_UNAVAILABLE }) {
            assertTrue("$kind should offer a retry", isRetryable(kind))
        }
    }

    private fun kindOf(status: Int): ErrorKind =
        (ApiResult.HttpError(status).toUiState() as UiState.Error).kind

    private fun shardKindOf(status: Int): ErrorKind =
        (ApiResult.HttpError(status).toShardUiState() as UiState.Error).kind
}
