namespace GmRelay.Bot.Tests.Web;

public sealed class PublicClubPagesTests
{
    [Fact]
    public async Task MigrationV026_ShouldAddPublicationControls()
    {
        var migration = await ReadRepositoryFileAsync("src/GmRelay.Bot/Migrations/V026__add_public_club_pages.sql");

        Assert.Contains("public_slug", migration, StringComparison.Ordinal);
        Assert.Contains("public_schedule_enabled", migration, StringComparison.Ordinal);
        Assert.Contains("is_public", migration, StringComparison.Ordinal);
        Assert.Contains("ux_game_groups_public_slug", migration, StringComparison.Ordinal);
        Assert.Contains("ix_sessions_public_schedule", migration, StringComparison.Ordinal);
    }

    [Fact]
    public async Task MigrationV030_ShouldAddClubMembershipsAndPublicationMode()
    {
        var migration = await ReadRepositoryFileAsync("src/GmRelay.Bot/Migrations/V030__add_club_memberships_and_publication_mode.sql");

        Assert.Contains("CREATE TABLE club_memberships", migration, StringComparison.Ordinal);
        Assert.Contains("status", migration, StringComparison.Ordinal);
        Assert.Contains("role", migration, StringComparison.Ordinal);
        Assert.Contains("ux_club_memberships_one_active", migration, StringComparison.Ordinal);
        Assert.Contains("publication_mode", migration, StringComparison.Ordinal);
        Assert.Contains("DROP COLUMN is_public", migration, StringComparison.Ordinal);
        Assert.Contains("portfolio_games", migration, StringComparison.Ordinal);
    }

    [Fact]
    public async Task PublicPages_ShouldExposeReadOnlyRoutesWithoutPrivateSessionData()
    {
        var publicClubPage = await ReadRepositoryFileAsync("src/GmRelay.Web/Components/Pages/PublicClub.razor");
        var publicSessionPage = await ReadRepositoryFileAsync("src/GmRelay.Web/Components/Pages/PublicSession.razor");

        Assert.Contains("@page \"/club/{Slug}\"", publicClubPage, StringComparison.Ordinal);
        Assert.Contains("@page \"/s/{SessionId:guid}\"", publicSessionPage, StringComparison.Ordinal);
        Assert.Contains("@layout PublicLayout", publicClubPage, StringComparison.Ordinal);
        Assert.Contains("@layout PublicLayout", publicSessionPage, StringComparison.Ordinal);
        Assert.DoesNotContain("@attribute [Authorize]", publicClubPage, StringComparison.Ordinal);
        Assert.DoesNotContain("@attribute [Authorize]", publicSessionPage, StringComparison.Ordinal);
        Assert.DoesNotContain("JoinLink", publicClubPage, StringComparison.Ordinal);
        Assert.DoesNotContain("JoinLink", publicSessionPage, StringComparison.Ordinal);
        Assert.DoesNotContain("WebParticipant", publicClubPage, StringComparison.Ordinal);
        Assert.DoesNotContain("WebParticipant", publicSessionPage, StringComparison.Ordinal);
    }

    [Fact]
    public async Task SessionStore_ShouldFilterPublicPagesByGroupAndSessionPublication()
    {
        var sessionStore = await ReadRepositoryFileAsync("src/GmRelay.Web/Services/ISessionStore.cs");
        var service = await ReadRepositoryFileAsync("src/GmRelay.Web/Services/SessionService.cs");

        Assert.Contains("GetPublicClubBySlugAsync", sessionStore, StringComparison.Ordinal);
        Assert.Contains("GetPublicSessionAsync", sessionStore, StringComparison.Ordinal);
        Assert.Contains("SetSessionPublicationModeAsync", sessionStore, StringComparison.Ordinal);
        Assert.Contains("SetBatchPublicationModeAsync", sessionStore, StringComparison.Ordinal);
        Assert.Contains("g.public_schedule_enabled = true", service, StringComparison.Ordinal);
        Assert.Contains("s.publication_mode IN ('Catalog', 'Both')", service, StringComparison.Ordinal);
        Assert.Contains("s.status <> @Cancelled", service, StringComparison.Ordinal);
        Assert.DoesNotContain("p.display_name AS DisplayName,\r\n                   p.external_username", PublicQuerySection(service), StringComparison.Ordinal);
    }

    [Fact]
    public async Task Dashboard_ShouldManagePublicationSettings()
    {
        var groupDetailsPage = await ReadRepositoryFileAsync("src/GmRelay.Web/Components/Pages/GroupDetails.razor");
        var authorizedService = await ReadRepositoryFileAsync("src/GmRelay.Web/Services/AuthorizedSessionService.cs");

        Assert.Contains("UpdatePublicGroupSettingsForCurrentUserAsync", groupDetailsPage, StringComparison.Ordinal);
        Assert.Contains("SetSessionPublicationModeForCurrentUserAsync", groupDetailsPage, StringComparison.Ordinal);
        Assert.Contains("SetBatchPublicationModeForCurrentUserAsync", groupDetailsPage, StringComparison.Ordinal);
        Assert.Contains("PublicSessionUrl", groupDetailsPage, StringComparison.Ordinal);
        Assert.Contains("NormalizePublicSlug", authorizedService, StringComparison.Ordinal);
        Assert.Contains("IsGroupManagerAsync", authorizedService, StringComparison.Ordinal);
    }

    private static string PublicQuerySection(string source)
    {
        var start = source.IndexOf("GetPublicClubBySlugAsync", StringComparison.Ordinal);
        var end = source.IndexOf("public async Task<bool> IsGroupManagerAsync", StringComparison.Ordinal);
        return source[start..end];
    }

    private static async Task<string> ReadRepositoryFileAsync(string relativePath)
    {
        var directory = new DirectoryInfo(AppContext.BaseDirectory);
        while (directory is not null)
        {
            var candidate = Path.Combine(directory.FullName, relativePath);
            if (File.Exists(candidate))
            {
                return await File.ReadAllTextAsync(candidate);
            }

            directory = directory.Parent;
        }

        throw new FileNotFoundException($"Could not locate repository file '{relativePath}'.");
    }
}
