using Dapper;
using GmRelay.Shared.Domain;
using GmRelay.Shared.Platform;
using GmRelay.Shared.Rendering;
using Npgsql;

namespace GmRelay.Shared.Features.Sessions.CreateSession;

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

public sealed class CreateSessionHandler(
    NpgsqlDataSource dataSource)
{
    public async Task<CreateSessionResult> HandleAsync(CreateSessionCommand command, CancellationToken ct)
    {
        await using var connection = await dataSource.OpenConnectionAsync(ct);
        await using var transaction = await connection.BeginTransactionAsync(ct);

        var transactionCommitted = false;
        try
        {
            var platform = command.User.Platform.ToString();
            var externalUserId = command.User.ExternalUserId;
            var displayName = command.User.DisplayName;
            var externalUsername = command.User.ExternalUsername;

            await connection.ExecuteAsync(
                """
                INSERT INTO players (display_name, platform, external_user_id, external_username)
                VALUES (@Name, @Platform, @ExternalId, @Username)
                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 { ExternalId = externalUserId, Name = displayName, Username = externalUsername, Platform = platform },
                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.platform = @Platform
                             AND p.external_user_id = @ExternalGmId
                       ) AS CanManage
                FROM game_groups g
                WHERE g.platform = @Platform
                  AND g.external_group_id = @ExternalGroupId
                """,
                new { Platform = platform, ExternalGroupId = command.Group.ExternalGroupId, ExternalGmId = externalUserId },
                transaction);

            Guid groupId;
            if (existingGroup is null)
            {
                groupId = await connection.ExecuteScalarAsync<Guid>(
                    """
                    INSERT INTO game_groups (name, platform, external_group_id, external_channel_id)
                    VALUES (@ChatName, @Platform, @ExternalGroupId, @ExternalChannelId)
                    RETURNING id;
                    """,
                    new
                    {
                        Platform = platform,
                        ExternalGroupId = command.Group.ExternalGroupId,
                        ExternalChannelId = command.Group.ExternalChannelId,
                        ChatName = command.Group.DisplayName
                    },
                    transaction);

                await connection.ExecuteAsync(
                    """
                    INSERT INTO group_managers (group_id, player_id, role)
                    SELECT @GroupId, p.id, @OwnerRole
                    FROM players p
                    WHERE p.platform = @Platform
                      AND p.external_user_id = @ExternalGmId
                    ON CONFLICT (group_id, player_id) DO NOTHING
                    """,
                    new
                    {
                        GroupId = groupId,
                        Platform = platform,
                        ExternalGmId = externalUserId,
                        OwnerRole = GroupManagerRoleExtensions.OwnerValue
                    },
                    transaction);
            }
            else
            {
                if (!existingGroup.CanManage)
                {
                    await transaction.RollbackAsync(ct);
                    return new CreateSessionResult(
                        false,
                        "⛔ Только owner или co-GM этой группы может создавать игровые сессии.",
                        null,
                        null,
                        null,
                        Array.Empty<string>());
                }

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

            var batchId = Guid.NewGuid();
            var sessions = new List<SessionBatchDto>();
            var orderedTimes = command.ScheduledTimes.OrderBy(v => v).ToList();

            foreach (var scheduledAt in orderedTimes)
            {
                var sessionId = await connection.ExecuteScalarAsync<Guid>(
                    """
                    INSERT INTO sessions (batch_id, group_id, title, join_link, scheduled_at, status, max_players, system, description, format, duration_minutes, is_one_shot, cover_image_url)
                    VALUES (@BatchId, @GroupId, @Title, @Link, @ScheduledAt, @Status, @MaxPlayers, @System, @Description, @Format, @DurationMinutes, @IsOneShot, @CoverImageUrl)
                    RETURNING id;
                    """,
                    new
                    {
                        BatchId = batchId,
                        GroupId = groupId,
                        command.Title,
                        Link = command.Link,
                        ScheduledAt = scheduledAt,
                        Status = SessionStatus.Planned,
                        MaxPlayers = command.MaxPlayers,
                        System = command.System?.ToString(),
                        command.Description,
                        command.Format,
                        DurationMinutes = command.DurationMinutes,
                        IsOneShot = command.IsOneShot,
                        CoverImageUrl = command.ImageReference
                    },
                    transaction);

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

            await transaction.CommitAsync(ct);
            transactionCommitted = true;

            var view = SessionBatchViewBuilder.Build(command.Title, sessions, Array.Empty<ParticipantBatchDto>());

            return new CreateSessionResult(
                true,
                null,
                view,
                batchId,
                groupId,
                Array.Empty<string>());
        }
        catch
        {
            if (!transactionCommitted)
            {
                await transaction.RollbackAsync(ct);
            }
            throw;
        }
    }
}
