using Dapper;
using GmRelay.Shared.Domain;
using GmRelay.Shared.Platform;
using GmRelay.Shared.Rendering;
using Npgsql;

namespace GmRelay.Shared.Features.Sessions.RescheduleSession;

public sealed class HandleRescheduleTimeInputHandler(
    NpgsqlDataSource dataSource)
{
    public async Task<HandleRescheduleTimeInputResult> HandleAsync(
        HandleRescheduleTimeInputCommand command, CancellationToken ct)
    {
        await using var connection = await dataSource.OpenConnectionAsync(ct);

        var platform = command.User.Platform.ToString();
        var externalGmId = command.User.ExternalUserId;
        var externalGroupId = command.Group.ExternalGroupId;

        var proposal = await connection.QuerySingleOrDefaultAsync<AwaitingProposalDto>(
            """
            SELECT rp.id AS Id, rp.session_id AS SessionId, s.title AS Title, s.scheduled_at AS CurrentScheduledAt,
                   s.batch_id AS BatchId, s.batch_message_id AS BatchMessageId,
                   g.external_group_id AS ExternalGroupId,
                   s.thread_id AS ThreadId,
                   s.notification_mode AS NotificationMode
            FROM reschedule_proposals rp
            JOIN sessions s ON s.id = rp.session_id
            JOIN game_groups g ON g.id = s.group_id
            WHERE rp.proposed_by_external_user_id = @ExternalGmId
              AND rp.status = 'AwaitingTime'
              AND g.platform = @Platform
              AND g.external_group_id = @ExternalGroupId
              AND EXISTS (
                  SELECT 1
                  FROM group_managers gm
                  JOIN players manager_player ON manager_player.id = gm.player_id
                  WHERE gm.group_id = s.group_id
                    AND manager_player.platform = @Platform
                    AND manager_player.external_user_id = @ExternalGmId
              )
            ORDER BY rp.created_at DESC
            LIMIT 1
            """,
            new { ExternalGmId = externalGmId, Platform = platform, ExternalGroupId = externalGroupId });

        if (proposal is null)
            return new HandleRescheduleTimeInputResult(false, false, null, null, null, null, [], [], [], null, default, null);

        if (!RescheduleVotingInput.TryParse(command.Text, DateTimeOffset.UtcNow, out var votingInput, out var parseError))
        {
            return new HandleRescheduleTimeInputResult(
                true, false, parseError, null, null, null, [], [], [], proposal.Title, proposal.CurrentScheduledAt, null);
        }

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

        if (participants.Count == 0)
        {
            var newTime = votingInput.Options[0];
            var view = await RescheduleImmediatelyAsync(connection, proposal, newTime, ct);
            var replyText =
                $"""✅ Сессия «{proposal.Title}» перенесена!\n\n📅 Новое время: <b>{newTime.ToOffset(TimeSpan.FromHours(3)).ToString("d MMMM yyyy, HH:mm", System.Globalization.CultureInfo.GetCultureInfo("ru-RU"))}</b> (МСК)\n\n<i>Участников нет — голосование не требуется.</i>""";
            return new HandleRescheduleTimeInputResult(
                true, true, replyText, view, null, null, [], [], [], proposal.Title, proposal.CurrentScheduledAt, proposal.BatchMessageId);
        }

        await using var transaction = await connection.BeginTransactionAsync(ct);
        var options = votingInput.Options
            .Select((proposedAt, index) => new RescheduleOptionDto(
                Guid.NewGuid(),
                index + 1,
                proposedAt))
            .ToList();

        await connection.ExecuteAsync(
            """
            UPDATE reschedule_proposals
            SET voting_deadline_at = @Deadline, status = 'Voting', vote_chat_id = @VoteChatId
            WHERE id = @Id
            """,
            new { votingInput.Deadline, VoteChatId = externalGroupId, Id = proposal.Id },
            transaction);

        foreach (var option in options)
        {
            await connection.ExecuteAsync(
                """
                INSERT INTO reschedule_options (id, proposal_id, proposed_at, display_order)
                VALUES (@OptionId, @ProposalId, @ProposedAt, @DisplayOrder)
                """,
                new
                {
                    option.OptionId,
                    ProposalId = proposal.Id,
                    option.ProposedAt,
                    option.DisplayOrder
                },
                transaction);
        }

        await transaction.CommitAsync(ct);

        return new HandleRescheduleTimeInputResult(
            true,
            false,
            null,
            null,
            proposal.Id,
            votingInput.Deadline,
            options,
            participants,
            [],
            proposal.Title,
            proposal.CurrentScheduledAt,
            null);
    }

    private static async Task<SessionBatchViewModel?> RescheduleImmediatelyAsync(
        NpgsqlConnection connection,
        AwaitingProposalDto proposal,
        DateTimeOffset newTime,
        CancellationToken ct)
    {
        await using var transaction = await connection.BeginTransactionAsync(ct);

        await connection.ExecuteAsync(
            """
            UPDATE sessions
            SET scheduled_at = @NewTime,
                status = @Status,
                confirmation_message_id = NULL,
                confirmation_sent_at = NULL,
                one_hour_reminder_processed_at = NULL,
                updated_at = now()
            WHERE id = @SessionId
            """,
            new { NewTime = newTime, proposal.SessionId, Status = SessionStatus.Planned },
            transaction);

        await connection.ExecuteAsync(
            "UPDATE reschedule_proposals SET proposed_at = @NewTime, status = 'Approved' WHERE id = @Id",
            new { NewTime = newTime, Id = proposal.Id },
            transaction);

        await transaction.CommitAsync(ct);

        var batchSessions = (await connection.QueryAsync<SessionBatchDto>(
            "SELECT id AS SessionId, scheduled_at AS ScheduledAt, status AS Status, max_players AS MaxPlayers, join_link AS JoinLink FROM sessions WHERE batch_id = @BatchId ORDER BY scheduled_at",
            new { proposal.BatchId })).ToList();

        var batchParticipants = (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 sp.player_id = p.id
            JOIN sessions s ON sp.session_id = s.id
            WHERE s.batch_id = @BatchId AND sp.is_gm = false
            ORDER BY sp.registration_status ASC, sp.created_at ASC, sp.responded_at ASC, p.created_at ASC
            """,
            new { proposal.BatchId })).ToList();

        return SessionBatchViewBuilder.Build(proposal.Title, batchSessions, batchParticipants);
    }
}
