using Dapper;
using GmRelay.DiscordBot.Infrastructure.Discord;
using GmRelay.Shared.Domain;
using GmRelay.Shared.Rendering;
using Npgsql;
using System.Globalization;

namespace GmRelay.DiscordBot.Features.Sessions;

public sealed record TimeParseResult(bool IsSuccess, DateTimeOffset Value, string? Error);

public sealed class DiscordNewSessionHandler(
    NpgsqlDataSource dataSource,
    DiscordPermissionChecker permissionChecker,
    ILogger<DiscordNewSessionHandler> logger)
{
    private static readonly TimeSpan MoscowOffset = TimeSpan.FromHours(3);

    public static TimeParseResult ParseTimeInput(string input)
    {
        var trimmed = input.Trim();

        if (DateTime.TryParseExact(
            trimmed,
            "yyyy-MM-dd HH:mm",
            CultureInfo.InvariantCulture,
            DateTimeStyles.None,
            out var dt1))
        {
            var offset = new DateTimeOffset(dt1, MoscowOffset).ToUniversalTime();
            if (offset < DateTimeOffset.UtcNow)
                return new TimeParseResult(false, default, "Дата находится в прошлом.");

            return new TimeParseResult(true, offset, null);
        }

        if (DateTime.TryParseExact(
            trimmed,
            "dd.MM.yyyy HH:mm",
            CultureInfo.InvariantCulture,
            DateTimeStyles.None,
            out var dt2))
        {
            var offset = new DateTimeOffset(dt2, MoscowOffset).ToUniversalTime();
            if (offset < DateTimeOffset.UtcNow)
                return new TimeParseResult(false, default, "Дата находится в прошлом.");

            return new TimeParseResult(true, offset, null);
        }

        return new TimeParseResult(false, default, "Некорректный формат даты. Используйте YYYY-MM-DD HH:mm или DD.MM.YYYY HH:mm");
    }

    public async Task<SessionBatchViewModel> HandleAsync(
        string guildId,
        string channelId,
        string groupName,
        ulong userId,
        string userDisplayName,
        ulong resolvedPermissions,
        ulong guildOwnerId,
        string title,
        DateTimeOffset scheduledAt,
        int? maxPlayers,
        string? joinLink,
        CancellationToken cancellationToken)
    {
        await using var connection = await dataSource.OpenConnectionAsync(cancellationToken);
        var displayGroupName = string.IsNullOrWhiteSpace(groupName) || string.Equals(groupName, guildId, StringComparison.Ordinal)
            ? title
            : groupName.Trim();

        var dbManagerUserIds = await connection.QueryAsync<ulong>(
            @"SELECT CAST(p.external_user_id AS BIGINT)
              FROM group_managers gm
              JOIN players p ON p.id = gm.player_id
              JOIN game_groups g ON g.id = gm.group_id
              WHERE g.platform = 'Discord'
                AND p.platform = 'Discord'
                AND g.external_group_id = @GuildId",
            new { GuildId = guildId });

        if (!permissionChecker.CanManageSchedule(guildOwnerId, userId, dbManagerUserIds, resolvedPermissions))
        {
            throw new UnauthorizedAccessException("⛔ Только owner, администратор или manager могут создавать сессии.");
        }

        await using var transaction = await connection.BeginTransactionAsync(cancellationToken);
        var transactionCommitted = false;
        try
        {
            await connection.ExecuteAsync(
                @"INSERT INTO players (display_name, platform, external_user_id, external_username)
                  VALUES (@Name, 'Discord', @UserId, @Name)
                  ON CONFLICT (platform, external_user_id)
                  WHERE platform IS NOT NULL AND external_user_id IS NOT NULL
                  DO UPDATE SET display_name = EXCLUDED.display_name,
                                external_username = EXCLUDED.external_username",
                new { Name = userDisplayName, UserId = userId.ToString() },
                transaction);

            var groupId = await connection.ExecuteScalarAsync<Guid>(
                @"INSERT INTO game_groups (name, platform, external_group_id, external_channel_id)
                  VALUES (@GroupName, 'Discord', @GuildId, @ChannelId)
                  ON CONFLICT (platform, external_group_id)
                  WHERE platform IS NOT NULL AND external_group_id IS NOT NULL
                  DO UPDATE SET name = EXCLUDED.name,
                                external_channel_id = COALESCE(EXCLUDED.external_channel_id, game_groups.external_channel_id)
                  RETURNING id",
                new { GroupName = displayGroupName, GuildId = guildId, ChannelId = channelId },
                transaction);

            await connection.ExecuteAsync(
                @"INSERT INTO group_managers (group_id, player_id, role)
                  SELECT @GroupId, p.id, @OwnerRole
                  FROM players p
                  WHERE p.platform = 'Discord' AND p.external_user_id = @UserId
                  ON CONFLICT (group_id, player_id) DO NOTHING",
                new { GroupId = groupId, UserId = userId.ToString(), OwnerRole = GroupManagerRoleExtensions.OwnerValue },
                transaction);

            var batchId = Guid.NewGuid();
            var sessionId = await connection.ExecuteScalarAsync<Guid>(
                @"INSERT INTO sessions (batch_id, group_id, title, join_link, scheduled_at, status, max_players)
                  VALUES (@BatchId, @GroupId, @Title, @Link, @ScheduledAt, @Status, @MaxPlayers)
                  RETURNING id",
                new
                {
                    BatchId = batchId,
                    GroupId = groupId,
                    Title = title,
                    Link = joinLink ?? string.Empty,
                    ScheduledAt = scheduledAt.UtcDateTime,
                    Status = SessionStatus.Planned,
                    MaxPlayers = maxPlayers
                },
                transaction);

            await transaction.CommitAsync(cancellationToken);
            transactionCommitted = true;
            logger.LogInformation("Created session {SessionId} in guild {GuildId}", sessionId, guildId);

            var sessions = new[] { new SessionBatchDto(sessionId, scheduledAt.UtcDateTime, SessionStatus.Planned, maxPlayers, joinLink ?? string.Empty) };
            return SessionBatchViewBuilder.Build(title, sessions, Array.Empty<ParticipantBatchDto>());
        }
        catch
        {
            if (!transactionCommitted)
            {
                await transaction.RollbackAsync(cancellationToken);
            }

            throw;
        }
    }
}
