using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using GmRelay.DiscordBot.Infrastructure.Discord;
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 NetCord;
using NetCord.Rest;

namespace GmRelay.DiscordBot.Features.Sessions.Wizard;

/// <summary>
/// Finalises a wizard draft by calling the shared
/// <see cref="CreateSessionHandler"/>. On success the original draft
/// message is overwritten with a "✅ Создано" confirmation; on failure
/// the user is offered Retry / Cancel buttons so the same draft can be
/// re-submitted without re-entering the wizard from scratch.
/// </summary>
public sealed class DiscordWizardSubmitter
{
    private const int MaxRetries = 3;

    private readonly CreateSessionHandler _shared;
    private readonly RestClient _rest;
    private readonly IWizardDraftRepository _drafts;
    private readonly IWizardContextStore _contextStore;
    private readonly ILogger<DiscordWizardSubmitter> _log;

    public DiscordWizardSubmitter(
        CreateSessionHandler shared,
        RestClient rest,
        IWizardDraftRepository drafts,
        IWizardContextStore contextStore,
        ILogger<DiscordWizardSubmitter> log)
    {
        _shared = shared;
        _rest = rest;
        _drafts = drafts;
        _contextStore = contextStore;
        _log = log;
    }

    /// <summary>
    /// Submit the draft to the shared handler. On a 1-shot failure we
    /// edit the draft message to show "retry / cancel" affordances and
    /// bump the in-payload retry counter; after <see cref="MaxRetries"/>
    /// consecutive failures the draft is deleted.
    /// </summary>
    public async Task SubmitAsync(WizardDraft draft, CancellationToken ct)
    {
        var payload = LoadPayload(draft);
        if (!IsComplete(payload, out var missing))
        {
            await EditDraftMessageAsync(
                draft,
                $"❌ Не заполнены поля: {missing}",
                RetryCancelActions(),
                ct);
            return;
        }

        try
        {
            var commands = BuildCommands(draft, payload);
            foreach (var cmd in commands)
            {
                await _shared.HandleAsync(cmd, ct);
            }
            var totalSessions = commands.Sum(c => c.ScheduledTimes.Count);
            await EditDraftMessageAsync(
                draft,
                $"✅ Создано: {totalSessions} {(totalSessions == 1 ? "сессия" : "сессий")}",
                Array.Empty<WizardAction>(),
                ct);
            await _drafts.DeleteAsync(draft.Id, ct);
            _contextStore.Remove(draft.Id);
        }
        catch (Exception ex)
        {
            _log.LogError(ex, "Submit failed for draft {DraftId}", draft.Id);
            payload.RetryCount += 1;
            SavePayload(draft, payload);
            if (payload.RetryCount >= MaxRetries)
            {
                await EditDraftMessageAsync(
                    draft,
                    "💥 Не удалось создать сессию после 3 попыток. Используйте /newsession-wizard, чтобы начать заново.",
                    Array.Empty<WizardAction>(),
                    ct);
                await _drafts.DeleteAsync(draft.Id, ct);
                _contextStore.Remove(draft.Id);
                return;
            }
            draft.UpdatedAt = DateTime.UtcNow;
            await _drafts.UpsertAsync(draft, ct);
            // The full exception (with stack trace, Postgres constraint
            // name, sometimes partial SQL) is already logged server-side
            // on line 86. Show the user a generic message — never leak
            // internal error strings to the Discord channel.
            await EditDraftMessageAsync(
                draft,
                $"💥 Не удалось создать сессию. Попробуйте ещё раз (попытка {payload.RetryCount}/{MaxRetries}).",
                RetryCancelActions(),
                ct);
        }
    }

    // ── Build shared commands ────────────────────────────────────────
    // Same shape as the Telegram submitter: pool → one command with N
    // times, single → 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.Discord,
            draft.OwnerId,
            DisplayName: string.Empty,
            ExternalUsername: null);
        var group = new PlatformGroup(
            PlatformKind.Discord,
            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("дата/время");
            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 System.Text.Json.JsonSerializer.Deserialize(
            draft.PayloadJson, WizardPayloadJsonContext.Default.WizardPayload) ?? new WizardPayload();
    }

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

    // ── Embed editing ────────────────────────────────────────────────
    private async Task EditDraftMessageAsync(
        WizardDraft draft, string text, IReadOnlyList<WizardAction> actions, CancellationToken ct)
    {
        if (!_contextStore.TryGet(draft.Id, out var ctx))
        {
            return;
        }
        if (!ulong.TryParse(ctx.ChannelId, NumberStyles.Integer, CultureInfo.InvariantCulture, out var channelId) ||
            !ulong.TryParse(ctx.MessageId, NumberStyles.Integer, CultureInfo.InvariantCulture, out var messageId))
        {
            return;
        }
        try
        {
            var embed = new EmbedProperties()
                .WithTitle("Мастер создания сессии")
                .WithDescription(Truncate(text, 3900))
                .WithColor(new Color(0x5865F2));
            var rows = BuildActionRowsFromActions(actions);
            await _rest.ModifyMessageAsync(
                channelId,
                messageId,
                options =>
                {
                    options.Embeds = new[] { embed };
                    options.Components = rows;
                });
        }
        catch (RestException ex)
        {
            _log.LogWarning(ex, "Failed to edit wizard message for draft {DraftId}", draft.Id);
        }
    }

    private static IReadOnlyList<IMessageComponentProperties> BuildActionRowsFromActions(
        IReadOnlyList<WizardAction> actions)
    {
        if (actions.Count == 0)
        {
            return Array.Empty<IMessageComponentProperties>();
        }
        var rows = new List<IMessageComponentProperties>();
        foreach (var chunk in actions.Chunk(5))
        {
            var row = new ActionRowProperties();
            foreach (var action in chunk)
            {
                var style = action.Style switch
                {
                    WizardActionStyle.Primary => ButtonStyle.Primary,
                    WizardActionStyle.Success => ButtonStyle.Success,
                    WizardActionStyle.Danger => ButtonStyle.Danger,
                    _ => ButtonStyle.Secondary,
                };
                row.Add(new ButtonProperties(action.Payload, action.Label, style));
            }
            rows.Add(row);
        }
        return rows;
    }

    private static string Truncate(string text, int max) =>
        text.Length <= max ? text : text[..max];

    private static IReadOnlyList<WizardAction> RetryCancelActions() => new[]
    {
        new WizardAction("🔁 Повторить", WizardCallbackData.Create(), WizardActionStyle.Primary),
        new WizardAction("❌ Отмена", WizardCallbackData.Cancel(), WizardActionStyle.Danger),
    };
}
