using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using GmRelay.Bot.Features.Sessions.CreateSession.Wizard;
using GmRelay.Shared.Domain;
using GmRelay.Shared.Features.Sessions.CreateSession;
using GmRelay.Shared.Features.Sessions.CreateSession.Wizard;
using GmRelay.Shared.Platform;
using Microsoft.Extensions.Logging;
using Telegram.Bot.Types;
using Telegram.Bot.Types.ReplyMarkups;
using SharedCreateSessionHandler = GmRelay.Shared.Features.Sessions.CreateSession.CreateSessionHandler;

namespace GmRelay.Bot.Features.Sessions.CreateSession;

/// <summary>
/// Wizard-driven entry point for game-session creation. Replaces the legacy
/// text-template parser. Exposes <see cref="StartWizardAsync"/> (called from
/// <c>/newsession</c>), <see cref="TryResumeAsync"/> (continue a draft), and
/// <see cref="SubmitDraftAsync"/> (finalize on "✅ Создать" callback).
/// </summary>
public sealed class CreateSessionHandler
{
    private const int MaxRetries = 3;

    private readonly IWizardDraftRepository _drafts;
    private readonly SharedCreateSessionHandler _shared;
    private readonly ITelegramWizardMessenger _messenger;
    private readonly ILogger<CreateSessionHandler> _log;

    public CreateSessionHandler(
        IWizardDraftRepository drafts,
        SharedCreateSessionHandler shared,
        ITelegramWizardMessenger messenger,
        ILogger<CreateSessionHandler> log)
    {
        _drafts = drafts;
        _shared = shared;
        _messenger = messenger;
        _log = log;
    }

    /// <summary>
    /// Entry point for <c>/newsession</c>. If a non-expired draft already exists for
    /// this (chat, thread, owner), returns <c>null</c> so the caller can render a
    /// "Continue / Start over / Cancel" menu.
    /// </summary>
    public async Task<WizardDraft?> StartWizardAsync(Message message, CancellationToken ct)
    {
        var existing = await _drafts.GetActiveAsync(
            message.Chat.Id, message.MessageThreadId, message.From?.Id ?? 0, ct);
        if (existing is not null)
        {
            return null;
        }

        var draft = new WizardDraft
        {
            Id = Guid.NewGuid(),
            ChatId = message.Chat.Id,
            MessageThreadId = message.MessageThreadId,
            OwnerTelegramId = message.From?.Id ?? 0,
            Step = WizardStepNames.Type,
            CreatedAt = DateTimeOffset.UtcNow,
            UpdatedAt = DateTimeOffset.UtcNow,
            ExpiresAt = DateTimeOffset.UtcNow.AddHours(24),
        };
        await _drafts.UpsertAsync(draft, ct);

        var (text, kb) = WizardStep.Render(draft, new WizardPayload());
        var msgId = await _messenger.SendGroupMessageAsync(
            draft.ChatId, draft.MessageThreadId, text, kb, ct);
        draft.DraftMessageId = msgId;
        draft.UpdatedAt = DateTimeOffset.UtcNow;
        await _drafts.UpsertAsync(draft, ct);
        return draft;
    }

    /// <summary>
    /// Resume an existing draft — returns the draft row so the caller can re-render.
    /// </summary>
    public Task<WizardDraft?> TryResumeAsync(Message message, CancellationToken ct) =>
        _drafts.GetActiveAsync(
            message.Chat.Id, message.MessageThreadId, message.From?.Id ?? 0, ct);

    /// <summary>
    /// Finalize: build shared command(s), call the shared handler, edit the wizard message.
    /// On failure, retry up to <see cref="MaxRetries"/> times before deleting the draft.
    /// </summary>
    public async Task SubmitDraftAsync(WizardDraft draft, CancellationToken ct)
    {
        var payload = LoadPayload(draft);
        if (!IsComplete(payload, out var missing))
        {
            await _messenger.EditMessageTextAsync(
                draft.ChatId, draft.MessageThreadId, draft.DraftMessageId ?? 0,
                $"❌ Не заполнены поля: {missing}", EmptyKeyboard(), ct);
            return;
        }

        var commands = BuildCommands(draft, payload);
        try
        {
            foreach (var cmd in commands)
            {
                await _shared.HandleAsync(cmd, ct);
            }
            var totalSessions = commands.Sum(c => c.ScheduledTimes.Count);
            await _messenger.EditMessageTextAsync(
                draft.ChatId, draft.MessageThreadId, draft.DraftMessageId ?? 0,
                $"✅ Создано: {totalSessions} {(totalSessions == 1 ? "сессия" : "сессий")}",
                EmptyKeyboard(), ct);
            await _drafts.DeleteAsync(draft.Id, ct);
        }
        catch (Exception ex)
        {
            _log.LogError(ex, "SubmitDraftAsync failed for draft {DraftId}", draft.Id);
            payload.RetryCount += 1;
            SavePayload(draft, payload);
            if (payload.RetryCount >= MaxRetries)
            {
                await _messenger.EditMessageTextAsync(
                    draft.ChatId, draft.MessageThreadId, draft.DraftMessageId ?? 0,
                    "💥 Не удалось создать сессию после 3 попыток. Используйте /newsession, чтобы начать заново.",
                    EmptyKeyboard(), ct);
                await _drafts.DeleteAsync(draft.Id, ct);
                return;
            }
            draft.UpdatedAt = DateTimeOffset.UtcNow;
            await _drafts.UpsertAsync(draft, ct);
            await _messenger.EditMessageTextAsync(
                draft.ChatId, draft.MessageThreadId, draft.DraftMessageId ?? 0,
                $"💥 Ошибка: {ex.Message}\nПопытка {payload.RetryCount}/{MaxRetries}.",
                RetryCancelKeyboard(), ct);
        }
    }

    // ── Build shared commands ────────────────────────────────────────
    // The shared handler creates one session per scheduled time in a single transaction
    // and assigns the same batch_id to all of them. A wizard pool therefore produces ONE
    // command with N times; a single-game wizard produces ONE command with one time.
    private static List<CreateSessionCommand> BuildCommands(WizardDraft draft, WizardPayload p)
    {
        if (p.Type == WizardCreationType.Pool && p.Pool is { } pool && pool.Slots.Count > 0)
        {
            return new List<CreateSessionCommand>
            {
                BuildCommand(
                    draft,
                    p,
                    pool.Slots.Select(s => s.ScheduledAt).ToList(),
                    MaxPlayersForPool(pool),
                    isOneShot: false)
            };
        }
        return new List<CreateSessionCommand>
        {
            BuildCommand(
                draft,
                p,
                new[] { p.Single?.ScheduledAt ?? default },
                p.Single?.MaxPlayers ?? 0,
                isOneShot: true)
        };
    }

    private static int MaxPlayersForPool(WizardPoolInput pool) =>
        pool.Slots.Count == 0 ? 0 : pool.Slots.Max(s => s.MaxPlayers);

    private static CreateSessionCommand BuildCommand(
        WizardDraft draft,
        WizardPayload p,
        IReadOnlyList<DateTimeOffset> scheduledTimes,
        int maxPlayers,
        bool isOneShot)
    {
        var gmId = draft.OwnerTelegramId;
        var user = new PlatformUser(
            PlatformKind.Telegram,
            gmId.ToString(System.Globalization.CultureInfo.InvariantCulture),
            DisplayName: string.Empty,
            ExternalUsername: null);
        var group = new PlatformGroup(
            PlatformKind.Telegram,
            draft.ChatId.ToString(System.Globalization.CultureInfo.InvariantCulture),
            DisplayName: string.Empty,
            ExternalChannelId: null,
            ExternalThreadId: draft.MessageThreadId?.ToString(System.Globalization.CultureInfo.InvariantCulture));
        return new CreateSessionCommand(
            User: user,
            Group: group,
            Title: p.Title ?? string.Empty,
            Link: string.Empty,
            ScheduledTimes: scheduledTimes,
            MaxPlayers: maxPlayers,
            ImageReference: p.ImageFileId ?? p.ImageUrl,
            System: ParseSystem(p.System),
            Description: p.Description,
            Format: null,
            DurationMinutes: p.DurationMinutes,
            IsOneShot: isOneShot);
    }

    private static GameSystem? ParseSystem(string? code)
    {
        if (string.IsNullOrWhiteSpace(code)) return null;
        return Enum.TryParse<GameSystem>(code, ignoreCase: true, out var sys) ? sys : null;
    }

    // ── Validation ───────────────────────────────────────────────────
    private static bool IsComplete(WizardPayload p, out string missing)
    {
        var missingFields = new List<string>();
        if (string.IsNullOrWhiteSpace(p.Title)) missingFields.Add("название");
        if (string.IsNullOrWhiteSpace(p.System)) missingFields.Add("система");
        if (!p.DurationMinutes.HasValue) missingFields.Add("длительность");
        if (p.Visibility is null) missingFields.Add("видимость");

        if (p.Type == WizardCreationType.Single)
        {
            if (p.Single?.ScheduledAt is null) missingFields.Add("дата/время");
            if (p.Single?.MaxPlayers is null) missingFields.Add("лимит мест");
        }
        else
        {
            if (p.Pool is null || p.Pool.Slots.Count == 0) missingFields.Add("слоты");
        }
        missing = string.Join(", ", missingFields);
        return missingFields.Count == 0;
    }

    // ── Payload I/O ──────────────────────────────────────────────────
    private static WizardPayload LoadPayload(WizardDraft draft)
    {
        if (string.IsNullOrEmpty(draft.PayloadJson)) return new WizardPayload();
        return JsonSerializer.Deserialize(draft.PayloadJson, WizardPayloadJsonContext.Default.WizardPayload) ?? new WizardPayload();
    }

    private static void SavePayload(WizardDraft draft, WizardPayload p)
    {
        draft.PayloadJson = JsonSerializer.Serialize(p, WizardPayloadJsonContext.Default.WizardPayload);
    }

    // ── Keyboards ────────────────────────────────────────────────────
    private static InlineKeyboardMarkup EmptyKeyboard() => new(Array.Empty<InlineKeyboardButton[]>());
    private static InlineKeyboardMarkup RetryCancelKeyboard() => new(new[]
    {
        new[] { InlineKeyboardButton.WithCallbackData("🔁 Повторить", WizardCallbackData.Create()) },
        new[] { InlineKeyboardButton.WithCallbackData("❌ Отмена",    WizardCallbackData.Cancel()) },
    });
}
