using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Dapper;
using GmRelay.Shared.Features.Sessions.CreateSession.Wizard;
using Npgsql;
using Telegram.Bot;
using Telegram.Bot.Types.ReplyMarkups;

namespace GmRelay.Bot.Features.Sessions.CreateSession.Wizard;

/// <summary>
/// Telegram-side implementation of <see cref="IWizardMessenger"/>.
/// Translates the platform-neutral wizard contracts into the
/// <c>Telegram.Bot</c> SDK calls. All Telegram-specific behaviour
/// (message editing, callback ack, group lookup) lives behind the
/// interface so the wizard core stays in <c>GmRelay.Shared</c>.
/// </summary>
public sealed class TelegramWizardMessenger(
    ITelegramBotClient bot,
    NpgsqlDataSource dataSource) : IWizardMessenger
{
    public async Task<string> EditDraftMessageAsync(
        WizardDraft draft,
        string text,
        IReadOnlyList<WizardAction> keyboard,
        CancellationToken ct)
    {
        if (!TryParseChatId(draft.ChatId, out var chatId))
        {
            throw new InvalidOperationException(
                $"Wizard draft {draft.Id} has un-parseable chat id '{draft.ChatId}'.");
        }
        if (!TryParseMessageId(draft.DraftMessageId, out var messageId))
        {
            // No draft message recorded yet — fall back to sending a new one.
            return await SendDraftMessageAsync(draft, text, keyboard, ct);
        }
        var msg = await bot.EditMessageText(
            chatId: chatId,
            messageId: messageId,
            text: text,
            replyMarkup: WizardStep.ToInlineKeyboard(keyboard),
            cancellationToken: ct);
        return msg.MessageId.ToString(System.Globalization.CultureInfo.InvariantCulture);
    }

    public async Task<string> SendDraftMessageAsync(
        WizardDraft draft,
        string text,
        IReadOnlyList<WizardAction> keyboard,
        CancellationToken ct)
    {
        if (!TryParseChatId(draft.ChatId, out var chatId))
        {
            throw new InvalidOperationException(
                $"Wizard draft {draft.Id} has un-parseable chat id '{draft.ChatId}'.");
        }
        int? threadId = TryParseThreadId(draft.MessageThreadId, out var parsedThread)
            ? parsedThread
            : null;

        var msg = await bot.SendMessage(
            chatId: chatId,
            text: text,
            messageThreadId: threadId,
            replyMarkup: WizardStep.ToInlineKeyboard(keyboard),
            cancellationToken: ct);
        return msg.MessageId.ToString(System.Globalization.CultureInfo.InvariantCulture);
    }

    public Task AnswerInteractionAsync(string interactionId, string? text, CancellationToken ct)
    {
        return bot.AnswerCallbackQuery(interactionId, text: text, cancellationToken: ct);
    }

    public async Task<IReadOnlyList<WizardClubOption>> GetOwnerClubsAsync(string ownerId, CancellationToken ct)
    {
        // Adjusted from the plan: this codebase models "clubs" as game_groups
        // (V001 created game_groups; V026 added public_slug; no `clubs` table exists,
        // and game_groups has no `club_id` FK). The picker therefore returns the
        // game_groups the owner manages as a GM (via group_managers), matching
        // the WizardClubOption contract (UUID id, name) used downstream.
        //
        // NativeAOT: Dapper.AOT 1.0.48 only generates interceptors for the
        // (sql, object?) extension overload — not the (CommandDefinition) overload.
        // The wizard reaches this method on the PickClub visibility step
        // (issue #112 follow-up); using CommandDefinition here would fall back
        // to Dapper.SqlMapper.CreateParamInfoGenerator, which uses Reflection.Emit
        // and throws PlatformNotSupportedException on AOT. Same root cause as
        // WizardDraftRepository.GetActiveAsync in v3.9.0, same fix pattern.
        const string sql = """
            SELECT g.id   AS ClubId,
                   g.name AS Name
            FROM game_groups g
            JOIN group_managers gm ON gm.group_id = g.id
            JOIN players p ON p.id = gm.player_id
            WHERE p.platform = @Platform
              AND p.external_user_id = @ExternalId
            GROUP BY g.id, g.name
            ORDER BY g.name
            """;
        await using var connection = await dataSource.OpenConnectionAsync(ct);
        var rows = await connection.QueryAsync<WizardClubOption>(
            sql,
            new { Platform = "Telegram", ExternalId = ownerId });
        return rows.AsList();
    }

    private static bool TryParseChatId(string raw, out long chatId)
    {
        if (long.TryParse(raw, System.Globalization.NumberStyles.Integer, System.Globalization.CultureInfo.InvariantCulture, out chatId))
        {
            return true;
        }
        chatId = 0;
        return false;
    }

    private static bool TryParseMessageId(string? raw, out int messageId)
    {
        if (raw is not null &&
            int.TryParse(raw, System.Globalization.NumberStyles.Integer, System.Globalization.CultureInfo.InvariantCulture, out messageId))
        {
            return true;
        }
        messageId = 0;
        return false;
    }

    private static bool TryParseThreadId(string? raw, out int threadId)
    {
        if (raw is not null &&
            int.TryParse(raw, System.Globalization.NumberStyles.Integer, System.Globalization.CultureInfo.InvariantCulture, out threadId))
        {
            return true;
        }
        threadId = 0;
        return false;
    }
}
