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

namespace GmRelay.Bot.Features.Sessions.RescheduleSession;

public sealed record HandleRescheduleVoteCommand(
    Guid OptionId,
    long TelegramUserId,
    string CallbackQueryId,
    long ChatId,
    int MessageId);

internal sealed record VoteProposalDto(
    Guid Id,
    Guid SessionId,
    DateTimeOffset VotingDeadlineAt,
    string Title,
    DateTime CurrentScheduledAt);

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

        var proposal = await connection.QuerySingleOrDefaultAsync<VoteProposalDto>(
            """
            SELECT rp.id AS Id,
                   rp.session_id AS SessionId,
                   rp.voting_deadline_at AS VotingDeadlineAt,
                   s.title AS Title,
                   s.scheduled_at AS CurrentScheduledAt
            FROM reschedule_options ro
            JOIN reschedule_proposals rp ON rp.id = ro.proposal_id
            JOIN sessions s ON s.id = rp.session_id
            WHERE ro.id = @OptionId AND rp.status = 'Voting'
            """,
            new { command.OptionId },
            transaction);

        if (proposal is null)
        {
            await bot.AnswerCallbackQuery(
                command.CallbackQueryId,
                "Голосование уже завершено или не найдено.",
                cancellationToken: ct);
            return;
        }

        if (proposal.VotingDeadlineAt <= DateTimeOffset.UtcNow)
        {
            await bot.AnswerCallbackQuery(
                command.CallbackQueryId,
                "Дедлайн уже прошёл. Результаты скоро будут применены.",
                showAlert: true,
                cancellationToken: ct);
            return;
        }

        var playerId = await connection.ExecuteScalarAsync<Guid?>(
            """
            SELECT p.id
            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 { proposal.SessionId, command.TelegramUserId, Active = ParticipantRegistrationStatus.Active },
            transaction);

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

        await connection.ExecuteAsync(
            """
            INSERT INTO reschedule_option_votes (proposal_id, player_id, option_id)
            VALUES (@ProposalId, @PlayerId, @OptionId)
            ON CONFLICT (proposal_id, player_id) DO UPDATE
            SET option_id = EXCLUDED.option_id,
                voted_at = now()
            """,
            new
            {
                ProposalId = proposal.Id,
                PlayerId = playerId.Value,
                command.OptionId
            },
            transaction);

        var participants = (await connection.QueryAsync<VoteParticipantDto>(
            """
            SELECT p.id AS PlayerId,
                   p.display_name AS DisplayName,
                   p.telegram_username AS TelegramUsername,
                   p.telegram_id AS TelegramId
            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 p.display_name
            """,
            new { proposal.SessionId, Active = ParticipantRegistrationStatus.Active },
            transaction)).ToList();

        var options = (await connection.QueryAsync<RescheduleOptionDto>(
            """
            SELECT id AS OptionId,
                   display_order AS DisplayOrder,
                   proposed_at AS ProposedAt
            FROM reschedule_options
            WHERE proposal_id = @ProposalId
            ORDER BY display_order
            """,
            new { ProposalId = proposal.Id },
            transaction)).ToList();

        var votes = (await connection.QueryAsync<RescheduleOptionVoteDto>(
            """
            SELECT rov.option_id AS OptionId,
                   p.id AS PlayerId,
                   p.display_name AS DisplayName,
                   p.telegram_username AS TelegramUsername
            FROM reschedule_option_votes rov
            JOIN players p ON p.id = rov.player_id
            WHERE rov.proposal_id = @ProposalId
            ORDER BY rov.voted_at, p.display_name
            """,
            new { ProposalId = proposal.Id },
            transaction)).ToList();

        await transaction.CommitAsync(ct);

        var voteText = HandleRescheduleTimeInputHandler.BuildVotingMessage(
            proposal.Title,
            proposal.CurrentScheduledAt,
            proposal.VotingDeadlineAt,
            options,
            participants,
            votes);
        var keyboard = HandleRescheduleTimeInputHandler.BuildVotingKeyboard(options);

        try
        {
            await bot.EditMessageText(
                chatId: command.ChatId,
                messageId: command.MessageId,
                text: voteText,
                parseMode: Telegram.Bot.Types.Enums.ParseMode.Html,
                replyMarkup: keyboard,
                cancellationToken: ct);
        }
        catch (Exception ex)
        {
            logger.LogWarning(ex, "Failed to update reschedule vote message for proposal {ProposalId}", proposal.Id);
        }

        await bot.AnswerCallbackQuery(
            command.CallbackQueryId,
            "Ваш голос учтён. До дедлайна его можно изменить.",
            cancellationToken: ct);
    }
}
