using System.Globalization;
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.SendJoinLink;

internal sealed record JoinLinkSessionRow(
    Guid Id,
    Guid GroupId,
    string Title,
    string JoinLink,
    DateTime ScheduledAt,
    string Platform,
    string ExternalGroupId,
    string DisplayName,
    string? ExternalChannelId,
    int? ThreadId,
    string NotificationMode);

internal sealed record JoinLinkPlayerRow(
    string Platform,
    string ExternalUserId,
    string DisplayName,
    string? ExternalUsername,
    string RsvpStatus,
    string RegistrationStatus,
    bool IsGm);

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

        var session = await connection.QuerySingleOrDefaultAsync<JoinLinkSessionRow>(
            """
            SELECT s.id,
                   s.group_id AS GroupId,
                   s.title,
                   s.join_link AS JoinLink,
                   s.scheduled_at AS ScheduledAt,
                   g.platform AS Platform,
                   g.external_group_id AS ExternalGroupId,
                   g.name AS DisplayName,
                   g.external_channel_id AS ExternalChannelId,
                   s.thread_id AS ThreadId,
                   s.notification_mode AS NotificationMode
            FROM sessions s
            JOIN game_groups g ON g.id = s.group_id
            WHERE s.id = @SessionId
              AND s.status = @Confirmed
              AND (
                    (g.platform = 'Telegram' AND s.link_message_id IS NULL)
                    OR (
                        g.platform <> 'Telegram'
                        AND NOT EXISTS (
                            SELECT 1
                            FROM platform_messages pm
                            WHERE pm.session_id = s.id
                              AND pm.platform = g.platform
                              AND pm.purpose = 'join_link'
                        )
                    )
                  )
            """,
            new { SessionId = sessionId, Confirmed = SessionStatus.Confirmed });

        if (session is null)
        {
            logger.LogWarning("Session {SessionId} not eligible for join link", sessionId);
            return;
        }

        var players = (await connection.QueryAsync<JoinLinkPlayerRow>(
            """
            SELECT p.platform AS Platform,
                   p.external_user_id AS ExternalUserId,
                   p.display_name AS DisplayName,
                   p.external_username AS ExternalUsername,
                   sp.rsvp_status AS RsvpStatus,
                   sp.registration_status AS RegistrationStatus,
                   sp.is_gm AS IsGm
            FROM session_participants sp
            JOIN players p ON p.id = sp.player_id
            WHERE sp.session_id = @SessionId
              AND sp.rsvp_status = @Confirmed
              AND sp.registration_status = @Active
            ORDER BY sp.created_at ASC
            """,
            new
            {
                SessionId = sessionId,
                Confirmed = RsvpStatus.Confirmed,
                Active = ParticipantRegistrationStatus.Active
            }))
            .Select(ToParticipant)
            .ToList();

        var group = CreateGroup(session);
        var message = await messenger.SendJoinLinkNotificationAsync(
            new PlatformJoinLinkNotification(
                group,
                session.Id,
                session.Title,
                session.ScheduledAt,
                session.JoinLink,
                players),
            ct);

        await connection.ExecuteAsync(
            """
            UPDATE sessions
            SET    link_message_id = @MessageId, updated_at = now()
            WHERE  id = @SessionId
            """,
            new
            {
                SessionId = sessionId,
                MessageId = TryGetTelegramMessageId(message)
            });

        await PersistPlatformMessageAsync(
            connection,
            message,
            session.GroupId,
            session.Id,
            batchId: null,
            purpose: "join_link");

        var mode = SessionNotificationModeExtensions.FromDatabaseValue(session.NotificationMode);
        if (mode.ShouldSendDirectMessages())
        {
            await directSender.SendAsync(
                PlatformDirectSessionNotificationKind.JoinLink,
                players.Select(p => p.User),
                session.Id,
                session.Title,
                session.ScheduledAt,
                session.JoinLink,
                actorDisplayName: null,
                reason: null,
                ct);
        }

        logger.LogInformation(
            "Join link sent for session {SessionId} ({Title}), platform={Platform}, message_id={MessageId}",
            sessionId,
            session.Title,
            message.Platform,
            message.ExternalMessageId);
    }

    private static PlatformSessionParticipant ToParticipant(JoinLinkPlayerRow row) =>
        new(
            new PlatformUser(
                ParsePlatform(row.Platform),
                row.ExternalUserId,
                row.DisplayName,
                row.ExternalUsername),
            row.RsvpStatus,
            row.RegistrationStatus,
            row.IsGm);

    private static PlatformGroup CreateGroup(JoinLinkSessionRow row) =>
        new(
            ParsePlatform(row.Platform),
            row.ExternalGroupId,
            row.DisplayName,
            row.ExternalChannelId,
            row.ThreadId?.ToString(CultureInfo.InvariantCulture));

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

    private static int? TryGetTelegramMessageId(PlatformMessageRef message) =>
        message.Platform == PlatformKind.Telegram &&
        int.TryParse(message.ExternalMessageId, NumberStyles.Integer, CultureInfo.InvariantCulture, out var messageId)
            ? messageId
            : null;

    private static Task PersistPlatformMessageAsync(
        NpgsqlConnection connection,
        PlatformMessageRef message,
        Guid groupId,
        Guid? sessionId,
        Guid? batchId,
        string purpose) =>
        connection.ExecuteAsync(
            """
            INSERT INTO platform_messages (
                platform,
                group_id,
                batch_id,
                session_id,
                external_channel_id,
                external_thread_id,
                external_message_id,
                purpose)
            VALUES (
                @Platform,
                @GroupId,
                @BatchId,
                @SessionId,
                @ExternalChannelId,
                @ExternalThreadId,
                @ExternalMessageId,
                @Purpose)
            """,
            new
            {
                Platform = message.Platform.ToString(),
                GroupId = groupId,
                BatchId = batchId,
                SessionId = sessionId,
                ExternalChannelId = message.ExternalGroupId,
                message.ExternalThreadId,
                message.ExternalMessageId,
                Purpose = purpose
            });
}
