/*
 * 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 org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test

/**
 * The declarative access-level menu filter (PLAN.md §5): one list gated by the
 * current session, not scattered `if role ==` checks.
 */
class MenuAccessTest {

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

    private fun routes(session: Session) =
        visibleEntries(APP_MENU, session).map { it.route }

    @Test fun anonymousSeesOnlyPublicEntries() {
        val visible = routes(Session.SignedOut)
        assertTrue(visible.contains(Routes.HOME))
        assertTrue(visible.contains(Routes.SHARD))
        // Signed-in surfaces are hidden.
        assertFalse(visible.contains(Routes.ACCOUNT))
    }

    @Test fun signedInPlayerSeesAccount() {
        val visible = routes(signedIn(Role.PLAYER))
        assertTrue(visible.contains(Routes.ACCOUNT))
        assertTrue(visible.contains(Routes.HOME))
    }

    @Test fun staffSeeThePlayerGameDataGroups() {
        // Staff are a superset of players: every staff role sees the PLAYER-access
        // game-data groups too (their own linked characters, via the role-agnostic
        // /player self-service surface), on top of their staff entries.
        val playerGroups = APP_MENU.filter { it.access == MenuAccess.PLAYER }.map { it.route }
        for (role in listOf(Role.ADMIN, Role.EDITOR, Role.MODERATOR)) {
            val visible = routes(signedIn(role))
            assertTrue("$role should see Account", visible.contains(Routes.ACCOUNT))
            assertTrue("$role should see the player game-data groups", playerGroups.all { visible.contains(it) })
        }
    }

    @Test fun publicEntryCountIsStableAcrossSessions() {
        val publicCount = APP_MENU.count { it.access == MenuAccess.PUBLIC }
        assertEquals(publicCount, routes(Session.SignedOut).size)
    }

    @Test fun roleFromWireMapsKnownAndUnknown() {
        assertEquals(Role.ADMIN, Role.fromWire("admin"))
        assertEquals(Role.PLAYER, Role.fromWire("player"))
        assertEquals(Role.UNKNOWN, Role.fromWire("superuser"))
        assertEquals(Role.UNKNOWN, Role.fromWire(null))
    }

    @Test fun playerAccessGatedFunction() {
        // A PLAYER-gated entry is visible to a player AND to every staff role
        // (staff superset), hidden only from an unrecognized role and anon.
        val entries = listOf(MenuEntry("game", 0, MenuAccess.PLAYER))
        for (role in listOf(Role.PLAYER, Role.ADMIN, Role.EDITOR, Role.MODERATOR)) {
            assertTrue("$role should see a PLAYER entry", visibleEntries(entries, signedIn(role)).isNotEmpty())
        }
        assertTrue(visibleEntries(entries, signedIn(Role.UNKNOWN)).isEmpty())
        assertTrue(visibleEntries(entries, Session.SignedOut).isEmpty())
    }

    @Test fun staffSeeTheAdminDashboardButPlayersDoNot() {
        // STAFF entries (M10) show for every staff role, never for a player or anon.
        for (role in listOf(Role.ADMIN, Role.EDITOR, Role.MODERATOR)) {
            assertTrue("$role should see the dashboard", routes(signedIn(role)).contains(Routes.ADMIN_DASHBOARD))
        }
        assertFalse(routes(signedIn(Role.PLAYER)).contains(Routes.ADMIN_DASHBOARD))
        assertFalse(routes(Session.SignedOut).contains(Routes.ADMIN_DASHBOARD))
    }

    @Test fun moderatorAccessIsAdminAndModeratorOnly() {
        // A synthetic MODERATOR-gated entry (moderation / support) is visible to
        // admin + moderator, but NOT editor, player, or anon.
        val entries = listOf(MenuEntry("mod", 0, MenuAccess.MODERATOR))
        assertTrue(visibleEntries(entries, signedIn(Role.ADMIN)).isNotEmpty())
        assertTrue(visibleEntries(entries, signedIn(Role.MODERATOR)).isNotEmpty())
        assertTrue(visibleEntries(entries, signedIn(Role.EDITOR)).isEmpty())
        assertTrue(visibleEntries(entries, signedIn(Role.PLAYER)).isEmpty())
        assertTrue(visibleEntries(entries, Session.SignedOut).isEmpty())
    }
}
