using Dapper;
using GmRelay.Shared.Domain;
using Npgsql;
using Telegram.Bot;
using Telegram.Bot.Types.ReplyMarkups;

namespace GmRelay.Bot.Features.Confirmation.HandleRsvp;

public sealed record HandleRsvpCommand(
    Guid SessionId,
    long TelegramUserId,
    string Status,
    string CallbackQueryId,
    long ChatId,
    int MessageId);

internal sealed record RsvpCounts(int Total, int Confirmed, int Declined);

internal sealed record SessionContext(
    string Title,
    DateTime ScheduledAt,
    string Status,
    long GmTelegramId,
    long TelegramChatId);

internal sealed record ParticipantRsvp(
    long TelegramId,
    string DisplayName,
    string? TelegramUsername,
    string RsvpStatus);

public sealed class HandleRsvpHandler(
    NpgsqlDataSource dataSource,
    ITelegramBotClient bot,
    ILogger<HandleRsvpHandler> logger)
{
    public async Task HandleAsync(HandleRsvpCommand command, CancellationToken ct)
    {
        await using var connection = await dataSource.OpenConnectionAsync(ct);
        await using var transaction = await connection.BeginTransactionAsync(ct);

        var participantExists = await connection.ExecuteScalarAsync<bool>(
            """
            SELECT EXISTS (
                SELECT 1
                FROM session_participants sp
                JOIN players p ON p.id = sp.player_id
                WHERE sp.session_id = @SessionId
                  AND p.telegram_id = @TelegramUserId
                  AND sp.is_gm = false
                  AND sp.registration_status = @Active
            )
            """,
            new { command.SessionId, command.TelegramUserId, Active = ParticipantRegistrationStatus.Active },
            transaction);

        if (!participantExists)
        {
            await bot.AnswerCallbackQuery(
                callbackQueryId: command.CallbackQueryId,
                text: "Вы не являетесь участником этой сессии.",
                cancellationToken: ct);
            return;
        }

        var updated = await connection.ExecuteAsync(
            """
            UPDATE session_participants
            SET    rsvp_status  = @Status,
                   responded_at = now()
            WHERE  session_id = @SessionId
              AND  player_id  = (SELECT id FROM players WHERE telegram_id = @TelegramUserId)
              AND  registration_status = @Active
              AND  rsvp_status != @Status
            """,
            new { command.SessionId, command.TelegramUserId, command.Status, Active = ParticipantRegistrationStatus.Active },
            transaction);

        if (updated == 0)
        {
            var alreadyText = command.Status == RsvpStatus.Confirmed
                ? "Вы уже подтвердили участие."
                : "Вы уже отказались от участия.";

            await bot.AnswerCallbackQuery(
                callbackQueryId: command.CallbackQueryId,
                text: alreadyText,
                cancellationToken: ct);
            return;
        }

        var session = await connection.QuerySingleAsync<SessionContext>(
            """
            SELECT s.title,
                   s.scheduled_at AS ScheduledAt,
                   s.status AS Status,
                   g.gm_telegram_id AS GmTelegramId,
                   g.telegram_chat_id AS TelegramChatId
            FROM sessions s
            JOIN game_groups g ON g.id = s.group_id
            WHERE s.id = @SessionId
            """,
            new { command.SessionId },
            transaction);

        if (command.Status == RsvpStatus.Declined)
        {
            var decision = RsvpFlowRules.Evaluate(command.Status, session.Status, totalParticipants: 0, confirmedParticipants: 0);

            if (decision.ShouldRevertSessionToConfirmationSent)
            {
                await connection.ExecuteAsync(
                    """
                    UPDATE sessions
                    SET    status = @ConfirmationSent, updated_at = now()
                    WHERE  id = @SessionId AND status = @Confirmed
                    """,
                    new
                    {
                        command.SessionId,
                        ConfirmationSent = SessionStatus.ConfirmationSent,
                        Confirmed = SessionStatus.Confirmed
                    },
                    transaction);
            }

            var declinedPlayer = await connection.QuerySingleAsync<string>(
                "SELECT display_name FROM players WHERE telegram_id = @TelegramUserId",
                new { command.TelegramUserId },
                transaction);

            await transaction.CommitAsync(ct);

            try
            {
                await bot.SendMessage(
                    chatId: session.GmTelegramId,
                    text: $"🚨 Отмена! {declinedPlayer} не сможет прийти на игру «{session.Title}».",
                    cancellationToken: ct);
            }
            catch (Exception ex)
            {
                logger.LogWarning(ex, "Failed to send decline alert to GM for session {SessionId}", command.SessionId);
            }

            await bot.AnswerCallbackQuery(
                callbackQueryId: command.CallbackQueryId,
                text: decision.CallbackText,
                cancellationToken: ct);
        }
        else
        {
            var counts = await connection.QuerySingleAsync<RsvpCounts>(
                """
                SELECT
                    count(*)                                         AS Total,
                    count(*) FILTER (WHERE rsvp_status = @Confirmed) AS Confirmed,
                    count(*) FILTER (WHERE rsvp_status = @Declined)  AS Declined
                FROM session_participants
                WHERE session_id = @SessionId AND is_gm = false
                  AND registration_status = @Active
                """,
                new
                {
                    command.SessionId,
                    Confirmed = RsvpStatus.Confirmed,
                    Declined = RsvpStatus.Declined,
                    Active = ParticipantRegistrationStatus.Active
                },
                transaction);

            var decision = RsvpFlowRules.Evaluate(command.Status, session.Status, counts.Total, counts.Confirmed);

            if (decision.ShouldMarkSessionConfirmed)
            {
                await connection.ExecuteAsync(
                    """
                    UPDATE sessions
                    SET    status = @Confirmed, updated_at = now()
                    WHERE  id = @SessionId
                    """,
                    new { command.SessionId, Confirmed = SessionStatus.Confirmed },
                    transaction);
            }

            await transaction.CommitAsync(ct);

            if (decision.ShouldNotifyGroup)
            {
                try
                {
                    await bot.SendMessage(
                        chatId: session.TelegramChatId,
                        text: $"🎉 Игра «{session.Title}» подтверждена! Все участники на месте.",
                        cancellationToken: ct);
                }
                catch (Exception ex)
                {
                    logger.LogWarning(ex, "Failed to send group confirmation for session {SessionId}", command.SessionId);
                }
            }

            if (decision.ShouldNotifyGm)
            {
                try
                {
                    await bot.SendMessage(
                        chatId: session.GmTelegramId,
                        text: $"✅ Все подтвердили участие в «{session.Title}» ({session.ScheduledAt.FormatMoscow()} МСК).",
                        cancellationToken: ct);
                }
                catch (Exception ex)
                {
                    logger.LogWarning(ex, "Failed to send GM confirmation for session {SessionId}", command.SessionId);
                }
            }

            await bot.AnswerCallbackQuery(
                callbackQueryId: command.CallbackQueryId,
                text: decision.CallbackText,
                cancellationToken: ct);
        }

        await UpdateConfirmationMessage(command, session, ct);
    }

    private async Task UpdateConfirmationMessage(HandleRsvpCommand command, SessionContext session, CancellationToken ct)
    {
        try
        {
            await using var connection = await dataSource.OpenConnectionAsync(ct);

            var participants = (await connection.QueryAsync<ParticipantRsvp>(
                """
                SELECT p.telegram_id AS TelegramId,
                       p.display_name AS DisplayName,
                       p.telegram_username AS TelegramUsername,
                       sp.rsvp_status AS RsvpStatus
                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
                ORDER BY sp.responded_at NULLS LAST
                """,
                new { command.SessionId, Active = ParticipantRegistrationStatus.Active })).ToList();

            var confirmed = participants.Where(p => p.RsvpStatus == RsvpStatus.Confirmed).ToList();
            var declined = participants.Where(p => p.RsvpStatus == RsvpStatus.Declined).ToList();
            var pending = participants.Where(p => p.RsvpStatus == RsvpStatus.Pending).ToList();

            var lines = new List<string>
            {
                $"🎲 Подтвердите участие в «{session.Title}»",
                $"📅 {session.ScheduledAt.FormatMoscow()} (МСК)",
                string.Empty
            };

            foreach (var participant in confirmed)
            {
                lines.Add($"   ✅ {FormatName(participant)}");
            }

            foreach (var participant in declined)
            {
                lines.Add($"   ❌ ~~{FormatName(participant)}~~");
            }

            foreach (var participant in pending)
            {
                lines.Add($"   ⏳ {FormatName(participant)}");
            }

            lines.Add(string.Empty);

            if (confirmed.Count == participants.Count)
            {
                lines.Add($"Статус: ✅ все подтвердили ({confirmed.Count}/{participants.Count})");
            }
            else if (declined.Count > 0)
            {
                lines.Add($"Статус: ⚠️ есть отказы ({confirmed.Count}/{participants.Count} подтвердили)");
            }
            else
            {
                lines.Add($"Статус: ожидаем подтверждения ({confirmed.Count}/{participants.Count})");
            }

            var text = string.Join("\n", lines);

            var replyMarkup = confirmed.Count == participants.Count
                ? null
                : new InlineKeyboardMarkup([
                    [
                        InlineKeyboardButton.WithCallbackData("✅ Буду", $"rsvp:confirm:{command.SessionId}"),
                        InlineKeyboardButton.WithCallbackData("❌ Не смогу", $"rsvp:decline:{command.SessionId}")
                    ]
                ]);

            await bot.EditMessageText(
                chatId: command.ChatId,
                messageId: command.MessageId,
                text: text,
                replyMarkup: replyMarkup,
                cancellationToken: ct);
        }
        catch (Exception ex)
        {
            logger.LogWarning(ex, "Failed to update confirmation message for session {SessionId}", command.SessionId);
        }
    }

    private static string FormatName(ParticipantRsvp participant) =>
        participant.TelegramUsername is not null ? $"@{participant.TelegramUsername}" : participant.DisplayName;
}
