using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
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 SharedCreateSessionHandler = GmRelay.Shared.Features.Sessions.CreateSession.CreateSessionHandler;

namespace GmRelay.Bot.Features.Sessions.CreateSession;

/// <summary>
/// Telegram-side entry point for the wizard-driven session creation
/// flow. Talks to the shared wizard through <see cref="IWizardMessenger"/>
/// and the platform-neutral <see cref="WizardDraft"/>. Keeps the
/// platform glue (mapping <c>Message</c> to draft fields, rendering
/// error keyboards, etc.) local to <c>GmRelay.Bot</c>.
/// </summary>
public sealed class CreateSessionHandler
{
    private const int MaxRetries = 3;
    private const string PlatformName = "Telegram";

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

    public CreateSessionHandler(
        IWizardDraftRepository drafts,
        SharedCreateSessionHandler shared,
        IWizardMessenger 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 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 ownerId = (message.From?.Id ?? 0).ToString(CultureInfo.InvariantCulture);
        var existing = await _drafts.GetActiveAsync(PlatformName, ownerId, ct);
        if (existing is not null)
        {
            return null;
        }

        var draft = new WizardDraft
        {
            Id = Guid.NewGuid(),
            ChatId = message.Chat.Id.ToString(CultureInfo.InvariantCulture),
            MessageThreadId = message.MessageThreadId?.ToString(CultureInfo.InvariantCulture),
            OwnerId = ownerId,
            Platform = PlatformName,
            Step = WizardStepNames.Type,
            CreatedAt = DateTime.UtcNow,
            UpdatedAt = DateTime.UtcNow,
            ExpiresAt = DateTime.UtcNow.AddHours(24),
        };
        await _drafts.UpsertAsync(draft, ct);

        var (text, actions) = WizardStepViewBuilder.Build(draft, new WizardPayload());
        var msgId = await _messenger.SendDraftMessageAsync(draft, text, actions, ct);
        draft.DraftMessageId = msgId;
        draft.UpdatedAt = DateTime.UtcNow;
        await _drafts.UpsertAsync(draft, ct);
        return draft;
    }

    /// <summary>
    /// Resume an existing draft — returns the draft row so the caller
    /// can re-render the resume/reset menu.
    /// </summary>
    public Task<WizardDraft?> TryResumeAsync(Message message, CancellationToken ct)
    {
        var ownerId = (message.From?.Id ?? 0).ToString(CultureInfo.InvariantCulture);
        return _drafts.GetActiveAsync(PlatformName, ownerId, 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.EditDraftMessageAsync(
                draft, $"❌ Не заполнены поля: {missing}", Array.Empty<WizardAction>(), 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.EditDraftMessageAsync(
                draft,
                $"✅ Создано: {totalSessions} {(totalSessions == 1 ? "сессия" : "сессий")}",
                Array.Empty<WizardAction>(),
                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.EditDraftMessageAsync(
                    draft,
                    "💥 Не удалось создать сессию после 3 попыток. Используйте /newsession, чтобы начать заново.",
                    Array.Empty<WizardAction>(),
                    ct);
                await _drafts.DeleteAsync(draft.Id, ct);
                return;
            }
            draft.UpdatedAt = DateTime.UtcNow;
            await _drafts.UpsertAsync(draft, ct);
            await _messenger.EditDraftMessageAsync(
                draft,
                $"💥 Ошибка: {ex.Message}\nПопытка {payload.RetryCount}/{MaxRetries}.",
                RetryCancelActions(),
                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,
                isOneShot: true),
        };
    }

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

    internal static CreateSessionCommand BuildCommand(
        WizardDraft draft,
        WizardPayload p,
        IReadOnlyList<DateTimeOffset> scheduledTimes,
        int? maxPlayers,
        bool isOneShot)
    {
        var user = new PlatformUser(
            PlatformKind.Telegram,
            draft.OwnerId,
            DisplayName: string.Empty,
            ExternalUsername: null);
        var group = new PlatformGroup(
            PlatformKind.Telegram,
            draft.ChatId,
            DisplayName: string.Empty,
            ExternalChannelId: null,
            ExternalThreadId: draft.MessageThreadId);
        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("дата/время");
            // MaxPlayers = null is a valid "♾ Без лимита" choice
            // (see GameCreationWizard.ApplyCapacityChoice "no_limit").
        }
        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 IReadOnlyList<WizardAction> RetryCancelActions() => new[]
    {
        new WizardAction("🔁 Повторить", WizardCallbackData.Create(), WizardActionStyle.Primary),
        new WizardAction("❌ Отмена",    WizardCallbackData.Cancel(), WizardActionStyle.Danger),
    };
}
