using Dapper;
using GmRelay.DiscordBot.Infrastructure.Discord;
using GmRelay.Shared.Domain;
using GmRelay.Shared.Rendering;
using Npgsql;

namespace GmRelay.DiscordBot.Features.Sessions;

internal sealed record DiscordSessionListItemDto(
    Guid Id, string Title, DateTime ScheduledAt, string Status, int? MaxPlayers,
    int PlayerCount, int WaitlistCount);

public sealed class DiscordListSessionsHandler(
    NpgsqlDataSource dataSource,
    DiscordPermissionChecker permissionChecker)
{
    public Task<SessionBatchViewModel?> BuildScheduleAsync(
        string guildId,
        string channelId,
        CancellationToken cancellationToken) =>
        BuildScheduleAsync(guildId, channelId, 0, 0, 0, cancellationToken);

    public async Task<SessionBatchViewModel?> BuildScheduleAsync(
        string guildId,
        string channelId,
        ulong userId,
        ulong resolvedPermissions,
        ulong guildOwnerId,
        CancellationToken cancellationToken)
    {
        await using var connection = await dataSource.OpenConnectionAsync(cancellationToken);

        var sessions = await connection.QueryAsync<DiscordSessionListItemDto>(
            @"SELECT s.id as Id, s.title as Title, s.scheduled_at as ScheduledAt, s.status as Status,
                     s.max_players as MaxPlayers,
                     COUNT(sp.id) FILTER (WHERE sp.is_gm = false AND sp.registration_status = @Active)::int as PlayerCount,
                     COUNT(sp.id) FILTER (WHERE sp.is_gm = false AND sp.registration_status = @Waitlisted)::int as WaitlistCount
              FROM sessions s
              JOIN game_groups g ON s.group_id = g.id
              LEFT JOIN session_participants sp ON s.id = sp.session_id
              WHERE g.platform = 'Discord'
                AND g.external_group_id = @GuildId
                AND s.status != @Cancelled
                AND s.scheduled_at > now() - interval '4 hours'
              GROUP BY s.id, s.title, s.scheduled_at, s.status, s.max_players
              ORDER BY s.scheduled_at ASC",
            new
            {
                GuildId = guildId,
                Cancelled = SessionStatus.Cancelled,
                Active = ParticipantRegistrationStatus.Active,
                Waitlisted = ParticipantRegistrationStatus.Waitlisted
            });

        var sessionList = sessions.ToList();
        if (sessionList.Count == 0)
            return null;

        var dbManagerUserIds = await connection.QueryAsync<ulong>(
            @"SELECT CAST(p.external_user_id AS BIGINT)
              FROM group_managers gm
              JOIN players p ON p.id = gm.player_id
              JOIN game_groups g ON g.id = gm.group_id
              WHERE g.platform = 'Discord' AND g.external_group_id = @GuildId",
            new { GuildId = guildId });

        var canManage = permissionChecker.CanManageSchedule(
            guildOwnerId,
            userId,
            dbManagerUserIds,
            resolvedPermissions);

        var sessionIds = sessionList.Select(s => s.Id).ToList();
        var participants = await connection.QueryAsync<ParticipantBatchDto>(
            @"SELECT sp.session_id as SessionId,
                     p.display_name as DisplayName,
                     p.external_username as TelegramUsername,
                     sp.registration_status as RegistrationStatus
              FROM session_participants sp
              JOIN players p ON p.id = sp.player_id
              WHERE sp.session_id = ANY(@SessionIds) AND sp.is_gm = false
              ORDER BY sp.registration_status ASC, sp.created_at ASC",
            new { SessionIds = sessionIds });

        var firstTitle = sessionList.First().Title;
        var batchDtos = sessionList.Select(s => new SessionBatchDto(
            s.Id, s.ScheduledAt, s.Status, s.MaxPlayers, "")).ToList();

        var view = SessionBatchViewBuilder.Build(firstTitle, batchDtos, participants.ToList());
        return canManage ? AddManagerActions(view) : view;
    }

    internal static SessionBatchViewModel AddManagerActions(SessionBatchViewModel view) =>
        view with
        {
            Sessions = view.Sessions
                .Select(session =>
                {
                    if (SessionStatus.IsCancelled(session.Status))
                        return session;

                    var actions = session.AvailableActions
                        .Concat([new AvailableAction("delete_session", $"Удалить {session.ScheduledAt.FormatMoscowShort()}", session.SessionId)])
                        .ToList();

                    return session with { AvailableActions = actions };
                })
                .ToList()
        };
}
