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

import com.runicgateway.app.core.auth.Role
import com.runicgateway.app.core.auth.Session
import com.runicgateway.app.core.auth.SessionUser
import com.runicgateway.app.data.repository.ShardFeature
import com.runicgateway.app.data.repository.ShardFeatures
import com.runicgateway.app.ui.shard.ShardBoard
import com.runicgateway.app.ui.shard.visibleBoards
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test

/**
 * The second gate on a shard entry (PLAN.md §5, §9 M11): the shard's admin-configured
 * visibility, independent of the session role. A signed-in admin still doesn't see a
 * board the shard doesn't publish, and an anonymous visitor still doesn't see a
 * signed-in entry however wide the feature config is.
 */
class MenuFeatureGatingTest {

    private fun signedIn(role: Role) =
        Session.SignedIn(SessionUser(id = 1, username = "u", role = role))

    private fun features(vararg visible: String) =
        ShardFeatures(level = "anonymous", visible = visible.toSet())

    private val shardEntry = MenuEntry("shard", 0, MenuAccess.PUBLIC, feature = ShardFeature.STATUS)
    private val plainEntry = MenuEntry("news", 0, MenuAccess.PUBLIC)

    @Test fun aShardEntryHidesWhenItsFeatureIsNotVisible() {
        val entries = listOf(plainEntry, shardEntry)

        val visible = visibleEntries(entries, Session.SignedOut, features("champs")).map { it.route }

        assertEquals(listOf("news"), visible)
    }

    @Test fun aShardEntryShowsWhenItsFeatureIsVisible() {
        val entries = listOf(plainEntry, shardEntry)

        val visible = visibleEntries(entries, Session.SignedOut, features("status")).map { it.route }

        assertEquals(listOf("news", "shard"), visible)
    }

    @Test fun unknownFeaturesShowEverythingTheRoleAllows() {
        // Fail open while the lookup is in flight or has failed — the server gates
        // regardless, so a link that briefly 403s beats a nav that flickers in.
        val entries = listOf(plainEntry, shardEntry)

        val visible = visibleEntries(entries, Session.SignedOut, features = null).map { it.route }

        assertEquals(listOf("news", "shard"), visible)
    }

    @Test fun theTwoGatesAreIndependent() {
        val staffShardEntry = MenuEntry("s", 0, MenuAccess.STAFF, feature = ShardFeature.HOUSES)
        val entries = listOf(staffShardEntry)

        // Right role, feature switched off → hidden.
        assertTrue(visibleEntries(entries, signedIn(Role.ADMIN), features("champs")).isEmpty())
        // Feature on, wrong role → hidden.
        assertTrue(visibleEntries(entries, signedIn(Role.PLAYER), features("houses")).isEmpty())
        // Both → shown.
        assertFalse(visibleEntries(entries, signedIn(Role.ADMIN), features("houses")).isEmpty())
    }

    @Test fun anAdminDoesNotBypassAFeatureGate() {
        // The rung the server placed the caller on is what /features already accounts
        // for. A staff role is not a licence to render a link to a disabled feature —
        // a disabled feature 404s for everyone.
        val entries = listOf(shardEntry)

        assertTrue(visibleEntries(entries, signedIn(Role.ADMIN), features()).isEmpty())
    }

    @Test fun everyShardMenuEntryDeclaresAFeature() {
        // A shard-derived entry with no feature name silently skips the gate. The app
        // menu's only such entry today is the Shard hub; this fails if one is added
        // without one.
        val shardRoutes = APP_MENU.filter { it.route == Routes.SHARD }
        assertTrue(shardRoutes.isNotEmpty())
        assertTrue(shardRoutes.all { it.feature != null })
    }

    // ── The hub's board tiles use the same gate ──────────────────────────
    @Test fun hubBoardsAreFilteredByFeature() {
        val visible = visibleBoards(features("champs", "houses"))

        assertEquals(listOf(ShardBoard.CHAMPS, ShardBoard.HOUSES), visible)
    }

    @Test fun hubBoardsShowAllWhenTheAnswerIsUnknown() {
        assertEquals(ShardBoard.entries.toList(), visibleBoards(null))
    }

    @Test fun eachBoardMapsToItsOwnFeature() {
        assertEquals(ShardFeature.CHAMPS, ShardBoard.CHAMPS.feature)
        assertEquals(ShardFeature.GUILDS, ShardBoard.GUILDS.feature)
        assertEquals(ShardFeature.GOVERNORS, ShardBoard.GOVERNORS.feature)
        assertEquals(ShardFeature.HOUSES, ShardBoard.HOUSES.feature)
    }
}
