using Dapper;
using GmRelay.Shared.Domain;
using GmRelay.Shared.Features.Notifications;
using GmRelay.Shared.Platform;
using Microsoft.Extensions.Logging;
using Npgsql;

namespace GmRelay.Shared.Features.Reminders.SendOneHourReminder;

internal sealed record OneHourReminderSessionRow(
    Guid Id,
    string Title,
    string JoinLink,
    DateTime ScheduledAt,
    string NotificationMode);

internal sealed record OneHourReminderRecipientRow(
    string Platform,
    string ExternalUserId,
    string DisplayName,
    string? ExternalUsername);

public sealed class SendOneHourReminderHandler(
    NpgsqlDataSource dataSource,
    PlatformDirectNotificationSender directSender,
    ILogger<SendOneHourReminderHandler> logger) : ISendOneHourReminderHandler
{
    public async Task HandleAsync(Guid sessionId, CancellationToken ct)
    {
        await using var connection = await dataSource.OpenConnectionAsync(ct);

        var session = await connection.QuerySingleOrDefaultAsync<OneHourReminderSessionRow>(
            """
            SELECT id,
                   title,
                   join_link AS JoinLink,
                   scheduled_at AS ScheduledAt,
                   notification_mode AS NotificationMode
            FROM sessions
            WHERE id = @SessionId
              AND status IN (@Confirmed, @ConfirmationSent)
              AND one_hour_reminder_processed_at IS NULL
            """,
            new
            {
                SessionId = sessionId,
                Confirmed = SessionStatus.Confirmed,
                ConfirmationSent = SessionStatus.ConfirmationSent
            });

        if (session is null)
        {
            logger.LogWarning("Session {SessionId} not eligible for one-hour reminder", sessionId);
            return;
        }

        var recipients = (await connection.QueryAsync<OneHourReminderRecipientRow>(
            """
            SELECT p.platform AS Platform,
                   p.external_user_id AS ExternalUserId,
                   p.display_name AS DisplayName,
                   p.external_username AS ExternalUsername
            FROM session_participants sp
            JOIN players p ON p.id = sp.player_id
            WHERE sp.session_id = @SessionId
              AND sp.is_gm = false
              AND sp.registration_status = @Active
              AND sp.rsvp_status != @Declined
            """,
            new
            {
                SessionId = sessionId,
                Active = ParticipantRegistrationStatus.Active,
                Declined = RsvpStatus.Declined
            }))
            .Select(row => new PlatformUser(
                ParsePlatform(row.Platform),
                row.ExternalUserId,
                row.DisplayName,
                row.ExternalUsername))
            .ToList();

        var mode = SessionNotificationModeExtensions.FromDatabaseValue(session.NotificationMode);
        if (mode.ShouldSendDirectMessages() && recipients.Count > 0)
        {
            await directSender.SendAsync(
                PlatformDirectSessionNotificationKind.OneHourReminder,
                recipients,
                session.Id,
                session.Title,
                session.ScheduledAt,
                session.JoinLink,
                actorDisplayName: null,
                reason: null,
                ct);
        }

        await connection.ExecuteAsync(
            """
            UPDATE sessions
            SET one_hour_reminder_processed_at = now(),
                updated_at = now()
            WHERE id = @SessionId
              AND one_hour_reminder_processed_at IS NULL
            """,
            new { SessionId = sessionId });

        logger.LogInformation(
            "One-hour reminder processed for session {SessionId} ({Title}) with mode {NotificationMode}",
            sessionId,
            session.Title,
            session.NotificationMode);
    }

    private static PlatformKind ParsePlatform(string platform) =>
        Enum.Parse<PlatformKind>(platform, ignoreCase: true);
}
