using Dapper;
using GmRelay.Shared.Domain;
using GmRelay.Shared.Rendering;
using Npgsql;
using Telegram.Bot;
using Telegram.Bot.Types;
using GmRelay.Bot.Infrastructure.Telegram;

namespace GmRelay.Bot.Features.Sessions.CreateSession;

internal sealed record SessionCreationGroupAccessDto(Guid GroupId, bool CanManage);

public sealed class CreateSessionHandler(
    NpgsqlDataSource dataSource,
    ITelegramBotClient botClient,
    ILogger<CreateSessionHandler> logger)
{
    public async Task HandleAsync(Message message, CancellationToken cancellationToken)
    {
        var parseResult = NewSessionCommandParser.Parse(message.Text ?? message.Caption, DateTimeOffset.UtcNow);

        foreach (var timeInput in parseResult.PastTimeInputs)
        {
            await botClient.SendMessage(
                message.Chat.Id,
                $"⚠️ Предупреждение: дата {timeInput} находится в прошлом и будет пропущена.",
                cancellationToken: cancellationToken);
        }

        foreach (var timeInput in parseResult.InvalidTimeInputs)
        {
            await botClient.SendMessage(
                message.Chat.Id,
                $"⚠️ Предупреждение: некорректный формат времени '{timeInput}'. Пропущено.",
                cancellationToken: cancellationToken);
        }

        foreach (var seatLimitInput in parseResult.InvalidSeatLimitInputs)
        {
            await botClient.SendMessage(
                message.Chat.Id,
                $"⚠️ Предупреждение: некорректный лимит мест '{seatLimitInput}'. Укажите целое число больше 0.",
                cancellationToken: cancellationToken);
        }

        foreach (var recurringInput in parseResult.InvalidRecurringInputs)
        {
            await botClient.SendMessage(
                message.Chat.Id,
                $"⚠️ Предупреждение: некорректный повтор расписания '{recurringInput}'. Укажите число игр 1-52 и шаг 1-365 дней.",
                cancellationToken: cancellationToken);
        }

        if (!parseResult.IsValid)
        {
            await botClient.SendMessage(
                chatId: message.Chat.Id,
                text: "❌ Не удалось распознать формат. Пожалуйста, используйте шаблон:\n\n/newsession\nНазвание: My Game\nВремя: 15.05.2026 19:30\nВремя: 22.05.2026 19:30\nМест: 4\nСсылка: https://link\nКартинка: https://cover\n\nДля повтора можно указать одну дату и строки:\nИгр: 4\nИнтервал: 7",
                cancellationToken: cancellationToken);
            return;
        }

        var title = parseResult.Title!;
        var link = parseResult.Link!;
        var imageReference = GetBatchImageReference(message, parseResult.ImageUrl);
        var gmId = message.From!.Id;
        var gmName = message.From.FirstName + (string.IsNullOrEmpty(message.From.LastName) ? string.Empty : $" {message.From.LastName}");
        var gmUsername = message.From.Username;

        var chatId = message.Chat.Id;
        var chatTitle = message.Chat.Title ?? "Private Chat";

        await using var connection = await dataSource.OpenConnectionAsync(cancellationToken);
        await using var transaction = await connection.BeginTransactionAsync(cancellationToken);

        try
        {
            await connection.ExecuteAsync(
                """
                INSERT INTO players (telegram_id, display_name, telegram_username)
                VALUES (@TgId, @Name, @Username)
                ON CONFLICT (telegram_id) DO UPDATE
                SET display_name = EXCLUDED.display_name,
                    telegram_username = EXCLUDED.telegram_username;
                """,
                new { TgId = gmId, Name = gmName, Username = gmUsername },
                transaction);

            var existingGroup = await connection.QuerySingleOrDefaultAsync<SessionCreationGroupAccessDto>(
                """
                SELECT g.id AS GroupId,
                       EXISTS (
                           SELECT 1
                           FROM group_managers gm
                           JOIN players p ON p.id = gm.player_id
                           WHERE gm.group_id = g.id
                             AND p.telegram_id = @GmId
                       ) AS CanManage
                FROM game_groups g
                WHERE g.telegram_chat_id = @ChatId
                """,
                new { ChatId = chatId, GmId = gmId },
                transaction);

            Guid groupId;
            if (existingGroup is null)
            {
                groupId = await connection.ExecuteScalarAsync<Guid>(
                    """
                    INSERT INTO game_groups (telegram_chat_id, name, gm_telegram_id)
                    VALUES (@ChatId, @ChatName, @GmId)
                    RETURNING id;
                    """,
                    new { ChatId = chatId, ChatName = chatTitle, GmId = gmId },
                    transaction);

                await connection.ExecuteAsync(
                    """
                    INSERT INTO group_managers (group_id, player_id, role)
                    SELECT @GroupId, p.id, @OwnerRole
                    FROM players p
                    WHERE p.telegram_id = @GmId
                    ON CONFLICT (group_id, player_id) DO NOTHING
                    """,
                    new { GroupId = groupId, GmId = gmId, OwnerRole = GroupManagerRoleExtensions.OwnerValue },
                    transaction);
            }
            else
            {
                if (!existingGroup.CanManage)
                {
                    await transaction.RollbackAsync(cancellationToken);
                    await botClient.SendMessage(
                        chatId,
                        "⛔ Только owner или co-GM этой группы может создавать игровые сессии.",
                        cancellationToken: cancellationToken);
                    return;
                }

                groupId = existingGroup.GroupId;
                await connection.ExecuteAsync(
                    "UPDATE game_groups SET name = @ChatName WHERE id = @GroupId",
                    new { ChatName = chatTitle, GroupId = groupId },
                    transaction);
            }

            int? messageThreadId = null;
            if (message.Chat.IsForum)
            {
                var topic = await botClient.CreateForumTopic(
                    chatId: chatId,
                    name: $"🎲 Игры: {title}",
                    cancellationToken: cancellationToken);
                messageThreadId = topic.MessageThreadId;
            }

            var batchId = Guid.NewGuid();
            var sessions = new List<SessionBatchDto>();

            foreach (var scheduledAt in parseResult.ScheduledTimes.OrderBy(value => value))
            {
                var sessionId = await connection.ExecuteScalarAsync<Guid>(
                    """
                    INSERT INTO sessions (batch_id, group_id, title, join_link, scheduled_at, status, thread_id, max_players)
                    VALUES (@BatchId, @GroupId, @Title, @Link, @ScheduledAt, @Status, @ThreadId, @MaxPlayers)
                    RETURNING id;
                    """,
                    new
                    {
                        BatchId = batchId,
                        GroupId = groupId,
                        Title = title,
                        Link = link,
                        ScheduledAt = scheduledAt,
                        ThreadId = messageThreadId,
                        MaxPlayers = parseResult.MaxPlayers,
                        Status = SessionStatus.Planned
                    },
                    transaction);

                sessions.Add(new SessionBatchDto(sessionId, scheduledAt.UtcDateTime, SessionStatus.Planned, parseResult.MaxPlayers, link));
            }

            await transaction.CommitAsync(cancellationToken);
            logger.LogInformation("Создан батч {BatchId} с {Count} сессиями в группе {GroupId}", batchId, sessions.Count, groupId);

            var view = SessionBatchViewBuilder.Build(title, sessions, Array.Empty<ParticipantBatchDto>());
            var renderResult = TelegramSessionBatchRenderer.Render(view);

            Message batchMessage;

            if (imageReference is not null && renderResult.Text.Length <= 1024)
            {
                // Картинка + расписание умещаются в одном Telegram-фото с подписью
                try
                {
                    batchMessage = await botClient.SendPhoto(
                        chatId: chatId,
                        messageThreadId: messageThreadId,
                        photo: InputFile.FromString(imageReference),
                        caption: renderResult.Text,
                        parseMode: Telegram.Bot.Types.Enums.ParseMode.Html,
                        replyMarkup: renderResult.Markup,
                        cancellationToken: cancellationToken);
                }
                catch (Exception ex)
                {
                    logger.LogWarning(ex, "Не удалось отправить картинку для батча {BatchId}, отправляем текстом", batchId);
                    batchMessage = await botClient.SendMessage(
                        chatId: chatId,
                        messageThreadId: messageThreadId,
                        text: renderResult.Text,
                        parseMode: Telegram.Bot.Types.Enums.ParseMode.Html,
                        replyMarkup: renderResult.Markup,
                        cancellationToken: cancellationToken);
                }
            }
            else
            {
                // Текст слишком длинный для caption — fallback на два сообщения
                if (imageReference is not null)
                {
                    try
                    {
                        await botClient.SendPhoto(
                            chatId: chatId,
                            messageThreadId: messageThreadId,
                            photo: InputFile.FromString(imageReference),
                            caption: $"🎲 {System.Net.WebUtility.HtmlEncode(title)}",
                            parseMode: Telegram.Bot.Types.Enums.ParseMode.Html,
                            cancellationToken: cancellationToken);
                    }
                    catch (Exception ex)
                    {
                        logger.LogWarning(ex, "Не удалось отправить картинку для батча {BatchId}", batchId);
                    }
                }

                batchMessage = await botClient.SendMessage(
                    chatId: chatId,
                    messageThreadId: messageThreadId,
                    text: renderResult.Text,
                    parseMode: Telegram.Bot.Types.Enums.ParseMode.Html,
                    replyMarkup: renderResult.Markup,
                    cancellationToken: cancellationToken);
            }

            await connection.ExecuteAsync(
                "UPDATE sessions SET batch_message_id = @MsgId WHERE batch_id = @BatchId",
                new { MsgId = batchMessage.MessageId, BatchId = batchId });

            try
            {
                await botClient.DeleteMessage(
                    chatId: chatId,
                    messageId: message.MessageId,
                    cancellationToken: cancellationToken);
            }
            catch (Exception ex)
            {
                logger.LogWarning(ex, "Не удалось удалить исходное сообщение {MessageId} в чате {ChatId}", message.MessageId, chatId);
            }
        }
        catch (Exception ex)
        {
            logger.LogError(ex, "Ошибка при создании сессии");
            await transaction.RollbackAsync(cancellationToken);
            await botClient.SendMessage(chatId, "💥 Произошла ошибка базы данных при создании сессии.", cancellationToken: cancellationToken);
        }
    }

    internal static string? GetBatchImageReference(Message message, string? parsedImageUrl)
    {
        var attachedPhotoFileId = message.Photo?
            .OrderByDescending(photo => photo.FileSize ?? 0)
            .ThenByDescending(photo => photo.Width * photo.Height)
            .FirstOrDefault()
            ?.FileId;

        if (!string.IsNullOrWhiteSpace(attachedPhotoFileId))
        {
            return attachedPhotoFileId;
        }

        return string.IsNullOrWhiteSpace(parsedImageUrl) ? null : parsedImageUrl.Trim();
    }
}
