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

import com.runicgateway.app.data.api.dto.ShardFeaturesDto
import com.runicgateway.app.data.api.fake.FakePublicApi
import com.runicgateway.app.util.httpError
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Test
import java.io.IOException

/**
 * The shard-visibility lookup (PLAN.md §9 M11). The behavior worth pinning is the
 * FAIL-OPEN direction: an unknown answer must show every entry, because the server
 * gates every call regardless and the alternative is a menu that flickers in.
 */
class ShardFeaturesRepositoryTest {

    private val api = FakePublicApi()
    private val repository = ShardFeaturesRepository(api)

    @Test fun refreshPublishesTheVisibleSetAndTheServersRung() = runTest {
        api.shardFeatures = ShardFeaturesDto(
            level = "player",
            features = listOf("status", "champs", "market"),
        )

        repository.refresh()

        val features = repository.features.value
        assertEquals("player", features?.level)
        assertEquals(setOf("status", "champs", "market"), features?.visible)
    }

    @Test fun aFeatureTheServerOmittedIsNotVisible() = runTest {
        api.shardFeatures = ShardFeaturesDto(level = "anonymous", features = listOf("status"))

        repository.refresh()

        assertTrue(canSee(repository.features.value, ShardFeature.STATUS))
        assertFalse(canSee(repository.features.value, ShardFeature.MARKET))
    }

    @Test fun aFailedLookupFallsBackToUnknownRatherThanEmpty() = runTest {
        // Empty and unknown are opposite answers: empty hides everything, unknown
        // shows everything. A failure must never be read as "this shard publishes
        // nothing".
        api.error = IOException("offline")

        repository.refresh()

        assertNull(repository.features.value)
        assertTrue(canSee(repository.features.value, ShardFeature.MARKET))
    }

    @Test fun aPreProtocol3WebsiteIs404AndReadsAsUnknown() = runTest {
        // The route does not exist before Protocol 3.0. That site has no visibility
        // framework at all, so "unknown" is exactly right and the menu behaves as it
        // did before M11.
        api.error = httpError(404)

        repository.refresh()

        assertNull(repository.features.value)
        assertTrue(canSee(repository.features.value, ShardFeature.CHAMPS))
    }

    @Test fun aFailedRefreshClearsAPreviouslyGoodAnswer() = runTest {
        api.shardFeatures = ShardFeaturesDto(level = "admin", features = listOf("status"))
        repository.refresh()
        assertEquals(setOf("status"), repository.features.value?.visible)

        // Signing out and failing to re-resolve must not leave the previous viewer's
        // (possibly wider) answer in place.
        api.error = httpError(500)
        repository.refresh()

        assertNull(repository.features.value)
    }

    @Test fun invalidateDropsTheCachedAnswer() = runTest {
        api.shardFeatures = ShardFeaturesDto(level = "staff", features = listOf("houses"))
        repository.refresh()
        assertEquals("staff", repository.features.value?.level)

        // A Settings → Server switch: the answer belonged to the old host.
        repository.invalidate()

        assertNull(repository.features.value)
    }

    @Test fun canSeeTreatsUnknownAsVisibleAndEmptyAsHidden() {
        assertTrue("unknown must fail open", canSee(null, ShardFeature.RULESET))
        assertFalse(
            "an explicit empty set hides everything",
            canSee(ShardFeatures(level = "anonymous", visible = emptySet()), ShardFeature.RULESET),
        )
    }
}
