using System.Data;
using Microsoft.EntityFrameworkCore;
using Ikar.Server.Data;

namespace Ikar.Server.Infrastructure.Storage;

public sealed class DatabaseSchemaBootstrapper(IkarDbContext dbContext)
{
    public async Task EnsureLatestAsync(CancellationToken cancellationToken = default)
    {
        await EnableWriteAheadLoggingAsync(cancellationToken);
        await EnsureMessageAttachmentsTableAsync(cancellationToken);
        await EnsurePushDevicesTableAsync(cancellationToken);
        await EnsureUserPrivacySettingsTableAsync(cancellationToken);
        await EnsureChatInviteLinksTableAsync(cancellationToken);
        await EnsureChatJoinRequestsTableAsync(cancellationToken);
        await EnsureChatTopicsTableAsync(cancellationToken);
        await EnsureBotsTableAsync(cancellationToken);
        await EnsureBotCommandsTableAsync(cancellationToken);
        await EnsureBotUpdatesTableAsync(cancellationToken);
        await EnsureModerationLogEntriesTableAsync(cancellationToken);
        await EnsureStoriesTablesAsync(cancellationToken);
        await EnsureMessageReactionsTableAsync(cancellationToken);
        await EnsureMessageViewsTableAsync(cancellationToken);
        await EnsureAndroidUpdateCompatibilityHitsTableAsync(cancellationToken);
        await EnsureChatMembersIndexesAsync(cancellationToken);
        await EnsureColumnAsync("ChatMembers", "LastDeliveredAt", "TEXT NULL", cancellationToken);
        await EnsureColumnAsync("ChatMembers", "LastReadAt", "TEXT NULL", cancellationToken);
        await EnsureColumnAsync("ChatMembers", "Role", "INTEGER NOT NULL DEFAULT 0", cancellationToken);
        await EnsureColumnAsync("ChatMembers", "CanPinMessages", "INTEGER NOT NULL DEFAULT 1", cancellationToken);
        await EnsureColumnAsync("ChatMembers", "CanDeleteMessages", "INTEGER NOT NULL DEFAULT 0", cancellationToken);
        await EnsureColumnAsync("ChatMembers", "CanInviteUsers", "INTEGER NOT NULL DEFAULT 1", cancellationToken);
        await EnsureColumnAsync("ChatMembers", "CanPostMessages", "INTEGER NOT NULL DEFAULT 1", cancellationToken);
        await EnsureColumnAsync("ChatMembers", "IsArchived", "INTEGER NOT NULL DEFAULT 0", cancellationToken);
        await EnsureColumnAsync("ChatMembers", "IsPinned", "INTEGER NOT NULL DEFAULT 0", cancellationToken);
        await EnsureColumnAsync("ChatMembers", "MutedUntil", "TEXT NULL", cancellationToken);
        await EnsureColumnAsync("ChatMembers", "FolderKey", "TEXT NULL", cancellationToken);
        await EnsureColumnAsync("UserSessions", "ClientPlatform", "TEXT NULL", cancellationToken);
        await EnsureColumnAsync("UserSessions", "ClientVersionCode", "INTEGER NULL", cancellationToken);
        await EnsureColumnAsync("UserSessions", "ClientVersionName", "TEXT NULL", cancellationToken);
        await EnsureColumnAsync("PushDevices", "ClientPlatform", "TEXT NULL", cancellationToken);
        await EnsureColumnAsync("PushDevices", "ClientVersionCode", "INTEGER NULL", cancellationToken);
        await EnsureColumnAsync("PushDevices", "ClientVersionName", "TEXT NULL", cancellationToken);
        await EnsureColumnAsync("Chats", "Description", "TEXT NULL", cancellationToken);
        await EnsureColumnAsync("Chats", "PublicUsername", "TEXT NULL", cancellationToken);
        await EnsureColumnAsync("Chats", "LinkedDiscussionChatId", "TEXT NULL", cancellationToken);
        await EnsureColumnAsync("Chats", "ChannelSignaturesEnabled", "INTEGER NOT NULL DEFAULT 0", cancellationToken);
        await EnsureColumnAsync("Chats", "TopicsEnabled", "INTEGER NOT NULL DEFAULT 0", cancellationToken);
        await EnsureColumnAsync("Chats", "HasProtectedContent", "INTEGER NOT NULL DEFAULT 0", cancellationToken);
        await EnsureColumnAsync("Chats", "DefaultMessageTtlSeconds", "INTEGER NULL", cancellationToken);
        await EnsureColumnAsync("Messages", "EditedAt", "TEXT NULL", cancellationToken);
        await EnsureColumnAsync("Messages", "DeletedAt", "TEXT NULL", cancellationToken);
        await EnsureColumnAsync("Messages", "ForwardedFromDisplayName", "TEXT NULL", cancellationToken);
        await EnsureColumnAsync("Messages", "ReplyToMessageId", "TEXT NULL", cancellationToken);
        await EnsureColumnAsync("Messages", "PinnedAt", "TEXT NULL", cancellationToken);
        await EnsureColumnAsync("Messages", "PinnedByUserId", "TEXT NULL", cancellationToken);
        await EnsureColumnAsync("Messages", "MediaAlbumId", "TEXT NULL", cancellationToken);
        await EnsureColumnAsync("Messages", "TopicId", "TEXT NULL", cancellationToken);
        await EnsureColumnAsync("Messages", "HasProtectedContent", "INTEGER NOT NULL DEFAULT 0", cancellationToken);
        await EnsureColumnAsync("Messages", "ExpiresAt", "TEXT NULL", cancellationToken);
        await EnsureColumnAsync("Messages", "TtlSeconds", "INTEGER NULL", cancellationToken);
        await EnsureColumnAsync("Messages", "PostedAsChannel", "INTEGER NOT NULL DEFAULT 0", cancellationToken);
        await EnsureColumnAsync("Messages", "AuthorSignature", "TEXT NULL", cancellationToken);
        await EnsureLegacyChannelPostsMarkedAsync(cancellationToken);
        await EnsureColumnAsync("MessageAttachments", "SortOrder", "INTEGER NOT NULL DEFAULT 0", cancellationToken);
        await EnsureMessagesPinnedIndexAsync(cancellationToken);
        await EnsureMessagesChatSenderSentAtIndexAsync(cancellationToken);
        await EnsureMessagesAlbumIndexAsync(cancellationToken);
        await EnsureMessagesTopicIndexAsync(cancellationToken);
        await EnsureChatsPublicUsernameIndexAsync(cancellationToken);
        await EnsureChatsLinkedDiscussionIndexAsync(cancellationToken);
        await EnsureMessageAttachmentsSortIndexAsync(cancellationToken);
        await EnsureChatMemberStateIndexesAsync(cancellationToken);
        await EnsureColumnAsync("Users", "IsBlocked", "INTEGER NOT NULL DEFAULT 0", cancellationToken);
        await EnsureColumnAsync("Users", "BlockedAt", "TEXT NULL", cancellationToken);
        await EnsureColumnAsync("Users", "PhoneNumber", "TEXT NULL", cancellationToken);
        await EnsureColumnAsync("Users", "NormalizedPhoneNumber", "TEXT NULL", cancellationToken);
        await EnsureColumnAsync("Users", "About", "TEXT NULL", cancellationToken);
        await EnsureColumnAsync("Users", "AvatarStoragePath", "TEXT NULL", cancellationToken);
        await EnsureColumnAsync("Users", "AvatarContentType", "TEXT NULL", cancellationToken);
        await EnsureColumnAsync("Users", "IsBot", "INTEGER NOT NULL DEFAULT 0", cancellationToken);
        await EnsureColumnAsync("UserPrivacySettings", "BlockedUserIds", "TEXT NOT NULL DEFAULT ''", cancellationToken);
        await EnsureColumnAsync("UserPrivacySettings", "AlwaysAllowUserIds", "TEXT NOT NULL DEFAULT ''", cancellationToken);
        await EnsureColumnAsync("UserPrivacySettings", "NeverAllowUserIds", "TEXT NOT NULL DEFAULT ''", cancellationToken);
        await EnsureMessagesExpiresAtIndexAsync(cancellationToken);
        await EnsureUsersPhoneIndexAsync(cancellationToken);
    }

    private async Task EnableWriteAheadLoggingAsync(CancellationToken cancellationToken)
    {
        await dbContext.Database.ExecuteSqlRawAsync("PRAGMA journal_mode = WAL;", cancellationToken);
    }

    private async Task EnsureMessageAttachmentsTableAsync(CancellationToken cancellationToken)
    {
        await dbContext.Database.ExecuteSqlRawAsync(
            """
            CREATE TABLE IF NOT EXISTS "MessageAttachments" (
                "Id" TEXT NOT NULL CONSTRAINT "PK_MessageAttachments" PRIMARY KEY,
                "MessageId" TEXT NOT NULL,
                "OriginalFileName" TEXT NOT NULL,
                "ContentType" TEXT NOT NULL,
                "FileSizeBytes" INTEGER NOT NULL,
                "StoragePath" TEXT NOT NULL,
                "UploadedAt" TEXT NOT NULL,
                CONSTRAINT "FK_MessageAttachments_Messages_MessageId" FOREIGN KEY ("MessageId") REFERENCES "Messages" ("Id") ON DELETE CASCADE
            );
            """,
            cancellationToken);

        await dbContext.Database.ExecuteSqlRawAsync(
            """
            CREATE INDEX IF NOT EXISTS "IX_MessageAttachments_MessageId" ON "MessageAttachments" ("MessageId");
            """,
            cancellationToken);
    }

    private async Task EnsurePushDevicesTableAsync(CancellationToken cancellationToken)
    {
        await dbContext.Database.ExecuteSqlRawAsync(
            """
            CREATE TABLE IF NOT EXISTS "PushDevices" (
                "Id" TEXT NOT NULL CONSTRAINT "PK_PushDevices" PRIMARY KEY,
                "UserId" TEXT NOT NULL,
                "Platform" INTEGER NOT NULL,
                "InstallationId" TEXT NOT NULL,
                "DeviceToken" TEXT NOT NULL,
                "DeviceName" TEXT NULL,
                "NotificationsEnabled" INTEGER NOT NULL,
                "CreatedAt" TEXT NOT NULL,
                "UpdatedAt" TEXT NOT NULL,
                CONSTRAINT "FK_PushDevices_Users_UserId" FOREIGN KEY ("UserId") REFERENCES "Users" ("Id") ON DELETE CASCADE
            );
            """,
            cancellationToken);

        await dbContext.Database.ExecuteSqlRawAsync(
            """
            CREATE UNIQUE INDEX IF NOT EXISTS "IX_PushDevices_InstallationId" ON "PushDevices" ("InstallationId");
            """,
            cancellationToken);

        await dbContext.Database.ExecuteSqlRawAsync(
            """
            CREATE INDEX IF NOT EXISTS "IX_PushDevices_UserId" ON "PushDevices" ("UserId");
            """,
            cancellationToken);
    }

    private async Task EnsureBotsTableAsync(CancellationToken cancellationToken)
    {
        await dbContext.Database.ExecuteSqlRawAsync(
            """
            CREATE TABLE IF NOT EXISTS "Bots" (
                "Id" TEXT NOT NULL CONSTRAINT "PK_Bots" PRIMARY KEY,
                "UserId" TEXT NOT NULL,
                "OwnerUserId" TEXT NOT NULL,
                "TokenHash" TEXT NOT NULL,
                "IsEnabled" INTEGER NOT NULL DEFAULT 1,
                "CreatedAt" TEXT NOT NULL,
                "UpdatedAt" TEXT NOT NULL,
                CONSTRAINT "FK_Bots_Users_UserId" FOREIGN KEY ("UserId") REFERENCES "Users" ("Id") ON DELETE CASCADE,
                CONSTRAINT "FK_Bots_Users_OwnerUserId" FOREIGN KEY ("OwnerUserId") REFERENCES "Users" ("Id") ON DELETE RESTRICT
            );
            """,
            cancellationToken);

        await dbContext.Database.ExecuteSqlRawAsync(
            """
            CREATE UNIQUE INDEX IF NOT EXISTS "IX_Bots_UserId" ON "Bots" ("UserId");
            """,
            cancellationToken);

        await dbContext.Database.ExecuteSqlRawAsync(
            """
            CREATE UNIQUE INDEX IF NOT EXISTS "IX_Bots_TokenHash" ON "Bots" ("TokenHash");
            """,
            cancellationToken);

        await dbContext.Database.ExecuteSqlRawAsync(
            """
            CREATE INDEX IF NOT EXISTS "IX_Bots_OwnerUserId" ON "Bots" ("OwnerUserId");
            """,
            cancellationToken);
    }

    private async Task EnsureBotCommandsTableAsync(CancellationToken cancellationToken)
    {
        await dbContext.Database.ExecuteSqlRawAsync(
            """
            CREATE TABLE IF NOT EXISTS "BotCommands" (
                "Id" TEXT NOT NULL CONSTRAINT "PK_BotCommands" PRIMARY KEY,
                "BotId" TEXT NOT NULL,
                "Command" TEXT NOT NULL,
                "Description" TEXT NOT NULL,
                "SortOrder" INTEGER NOT NULL,
                CONSTRAINT "FK_BotCommands_Bots_BotId" FOREIGN KEY ("BotId") REFERENCES "Bots" ("Id") ON DELETE CASCADE
            );
            """,
            cancellationToken);

        await dbContext.Database.ExecuteSqlRawAsync(
            """
            CREATE UNIQUE INDEX IF NOT EXISTS "IX_BotCommands_BotId_Command" ON "BotCommands" ("BotId", "Command");
            """,
            cancellationToken);
    }

    private async Task EnsureBotUpdatesTableAsync(CancellationToken cancellationToken)
    {
        await dbContext.Database.ExecuteSqlRawAsync(
            """
            CREATE TABLE IF NOT EXISTS "BotUpdates" (
                "Id" INTEGER NOT NULL CONSTRAINT "PK_BotUpdates" PRIMARY KEY AUTOINCREMENT,
                "BotId" TEXT NOT NULL,
                "ChatId" TEXT NOT NULL,
                "MessageId" TEXT NOT NULL,
                "CreatedAt" TEXT NOT NULL,
                CONSTRAINT "FK_BotUpdates_Bots_BotId" FOREIGN KEY ("BotId") REFERENCES "Bots" ("Id") ON DELETE CASCADE,
                CONSTRAINT "FK_BotUpdates_Messages_MessageId" FOREIGN KEY ("MessageId") REFERENCES "Messages" ("Id") ON DELETE CASCADE
            );
            """,
            cancellationToken);

        await dbContext.Database.ExecuteSqlRawAsync(
            """
            CREATE INDEX IF NOT EXISTS "IX_BotUpdates_BotId_Id" ON "BotUpdates" ("BotId", "Id");
            """,
            cancellationToken);

        await dbContext.Database.ExecuteSqlRawAsync(
            """
            CREATE UNIQUE INDEX IF NOT EXISTS "IX_BotUpdates_BotId_MessageId" ON "BotUpdates" ("BotId", "MessageId");
            """,
            cancellationToken);
    }

    private async Task EnsureModerationLogEntriesTableAsync(CancellationToken cancellationToken)
    {
        await dbContext.Database.ExecuteSqlRawAsync(
            """
            CREATE TABLE IF NOT EXISTS "ModerationLogEntries" (
                "Id" INTEGER NOT NULL CONSTRAINT "PK_ModerationLogEntries" PRIMARY KEY AUTOINCREMENT,
                "ChatId" TEXT NULL,
                "ActorUserId" TEXT NOT NULL,
                "TargetType" INTEGER NOT NULL,
                "TargetId" TEXT NULL,
                "Action" TEXT NOT NULL,
                "Reason" TEXT NULL,
                "CreatedAt" TEXT NOT NULL,
                CONSTRAINT "FK_ModerationLogEntries_Chats_ChatId" FOREIGN KEY ("ChatId") REFERENCES "Chats" ("Id") ON DELETE SET NULL,
                CONSTRAINT "FK_ModerationLogEntries_Users_ActorUserId" FOREIGN KEY ("ActorUserId") REFERENCES "Users" ("Id") ON DELETE RESTRICT
            );
            """,
            cancellationToken);

        await dbContext.Database.ExecuteSqlRawAsync(
            """
            CREATE INDEX IF NOT EXISTS "IX_ModerationLogEntries_ChatId_CreatedAt" ON "ModerationLogEntries" ("ChatId", "CreatedAt");
            """,
            cancellationToken);

        await dbContext.Database.ExecuteSqlRawAsync(
            """
            CREATE INDEX IF NOT EXISTS "IX_ModerationLogEntries_TargetType_TargetId" ON "ModerationLogEntries" ("TargetType", "TargetId");
            """,
            cancellationToken);
    }

    private async Task EnsureStoriesTablesAsync(CancellationToken cancellationToken)
    {
        await dbContext.Database.ExecuteSqlRawAsync(
            """
            CREATE TABLE IF NOT EXISTS "Stories" (
                "Id" TEXT NOT NULL CONSTRAINT "PK_Stories" PRIMARY KEY,
                "AuthorUserId" TEXT NOT NULL,
                "Text" TEXT NOT NULL,
                "Visibility" INTEGER NOT NULL DEFAULT 0,
                "HasProtectedContent" INTEGER NOT NULL DEFAULT 0,
                "CreatedAt" TEXT NOT NULL,
                "ExpiresAt" TEXT NOT NULL,
                "DeletedAt" TEXT NULL,
                CONSTRAINT "FK_Stories_Users_AuthorUserId" FOREIGN KEY ("AuthorUserId") REFERENCES "Users" ("Id") ON DELETE CASCADE
            );
            """,
            cancellationToken);

        await dbContext.Database.ExecuteSqlRawAsync(
            """
            CREATE TABLE IF NOT EXISTS "StoryAttachments" (
                "Id" TEXT NOT NULL CONSTRAINT "PK_StoryAttachments" PRIMARY KEY,
                "StoryId" TEXT NOT NULL,
                "OriginalFileName" TEXT NOT NULL,
                "ContentType" TEXT NOT NULL,
                "FileSizeBytes" INTEGER NOT NULL,
                "StoragePath" TEXT NOT NULL,
                "SortOrder" INTEGER NOT NULL DEFAULT 0,
                "UploadedAt" TEXT NOT NULL,
                CONSTRAINT "FK_StoryAttachments_Stories_StoryId" FOREIGN KEY ("StoryId") REFERENCES "Stories" ("Id") ON DELETE CASCADE
            );
            """,
            cancellationToken);

        await dbContext.Database.ExecuteSqlRawAsync(
            """
            CREATE TABLE IF NOT EXISTS "StoryViews" (
                "StoryId" TEXT NOT NULL,
                "ViewerUserId" TEXT NOT NULL,
                "ViewedAt" TEXT NOT NULL,
                CONSTRAINT "PK_StoryViews" PRIMARY KEY ("StoryId", "ViewerUserId"),
                CONSTRAINT "FK_StoryViews_Stories_StoryId" FOREIGN KEY ("StoryId") REFERENCES "Stories" ("Id") ON DELETE CASCADE,
                CONSTRAINT "FK_StoryViews_Users_ViewerUserId" FOREIGN KEY ("ViewerUserId") REFERENCES "Users" ("Id") ON DELETE CASCADE
            );
            """,
            cancellationToken);

        await dbContext.Database.ExecuteSqlRawAsync("""CREATE INDEX IF NOT EXISTS "IX_Stories_AuthorUserId_CreatedAt" ON "Stories" ("AuthorUserId", "CreatedAt");""", cancellationToken);
        await dbContext.Database.ExecuteSqlRawAsync("""CREATE INDEX IF NOT EXISTS "IX_Stories_ExpiresAt" ON "Stories" ("ExpiresAt");""", cancellationToken);
        await dbContext.Database.ExecuteSqlRawAsync("""CREATE INDEX IF NOT EXISTS "IX_StoryAttachments_StoryId_SortOrder" ON "StoryAttachments" ("StoryId", "SortOrder");""", cancellationToken);
        await dbContext.Database.ExecuteSqlRawAsync("""CREATE INDEX IF NOT EXISTS "IX_StoryViews_ViewerUserId" ON "StoryViews" ("ViewerUserId");""", cancellationToken);
    }

    private async Task EnsureAndroidUpdateCompatibilityHitsTableAsync(CancellationToken cancellationToken)
    {
        await dbContext.Database.ExecuteSqlRawAsync(
            """
            CREATE TABLE IF NOT EXISTS "AndroidUpdateCompatibilityHits" (
                "Id" INTEGER NOT NULL CONSTRAINT "PK_AndroidUpdateCompatibilityHits" PRIMARY KEY AUTOINCREMENT,
                "HitAt" TEXT NOT NULL,
                "Route" TEXT NOT NULL,
                "RequestPath" TEXT NOT NULL,
                "RequestedFileName" TEXT NULL,
                "ClientPlatform" TEXT NULL,
                "ClientVersionCode" INTEGER NULL,
                "ClientVersionName" TEXT NULL,
                "UserAgent" TEXT NULL,
                "RemoteIp" TEXT NULL
            );
            """,
            cancellationToken);

        await dbContext.Database.ExecuteSqlRawAsync(
            """
            CREATE INDEX IF NOT EXISTS "IX_AndroidUpdateCompatibilityHits_HitAt" ON "AndroidUpdateCompatibilityHits" ("HitAt");
            """,
            cancellationToken);

        await dbContext.Database.ExecuteSqlRawAsync(
            """
            CREATE INDEX IF NOT EXISTS "IX_AndroidUpdateCompatibilityHits_Route_HitAt" ON "AndroidUpdateCompatibilityHits" ("Route", "HitAt");
            """,
            cancellationToken);

        await dbContext.Database.ExecuteSqlRawAsync(
            """
            CREATE INDEX IF NOT EXISTS "IX_AndroidUpdateCompatibilityHits_ClientPlatform_ClientVersionCode_HitAt"
            ON "AndroidUpdateCompatibilityHits" ("ClientPlatform", "ClientVersionCode", "HitAt");
            """,
            cancellationToken);
    }

    private async Task EnsureMessageReactionsTableAsync(CancellationToken cancellationToken)
    {
        await dbContext.Database.ExecuteSqlRawAsync(
            """
            CREATE TABLE IF NOT EXISTS "MessageReactions" (
                "Id" TEXT NOT NULL CONSTRAINT "PK_MessageReactions" PRIMARY KEY,
                "MessageId" TEXT NOT NULL,
                "UserId" TEXT NOT NULL,
                "Emoji" TEXT NOT NULL,
                "CreatedAt" TEXT NOT NULL,
                CONSTRAINT "FK_MessageReactions_Messages_MessageId" FOREIGN KEY ("MessageId") REFERENCES "Messages" ("Id") ON DELETE CASCADE,
                CONSTRAINT "FK_MessageReactions_Users_UserId" FOREIGN KEY ("UserId") REFERENCES "Users" ("Id") ON DELETE CASCADE
            );
            """,
            cancellationToken);

        await dbContext.Database.ExecuteSqlRawAsync(
            """
            CREATE INDEX IF NOT EXISTS "IX_MessageReactions_MessageId" ON "MessageReactions" ("MessageId");
            """,
            cancellationToken);

        await dbContext.Database.ExecuteSqlRawAsync(
            """
            CREATE UNIQUE INDEX IF NOT EXISTS "IX_MessageReactions_MessageId_UserId" ON "MessageReactions" ("MessageId", "UserId");
            """,
            cancellationToken);

        await dbContext.Database.ExecuteSqlRawAsync(
            """
            CREATE INDEX IF NOT EXISTS "IX_Messages_ReplyToMessageId" ON "Messages" ("ReplyToMessageId");
            """,
            cancellationToken);
    }

    private async Task EnsureMessageViewsTableAsync(CancellationToken cancellationToken)
    {
        await dbContext.Database.ExecuteSqlRawAsync(
            """
            CREATE TABLE IF NOT EXISTS "MessageViews" (
                "MessageId" TEXT NOT NULL,
                "ViewerUserId" TEXT NOT NULL,
                "ViewedAt" TEXT NOT NULL,
                CONSTRAINT "PK_MessageViews" PRIMARY KEY ("MessageId", "ViewerUserId"),
                CONSTRAINT "FK_MessageViews_Messages_MessageId" FOREIGN KEY ("MessageId") REFERENCES "Messages" ("Id") ON DELETE CASCADE,
                CONSTRAINT "FK_MessageViews_Users_ViewerUserId" FOREIGN KEY ("ViewerUserId") REFERENCES "Users" ("Id") ON DELETE CASCADE
            );
            """,
            cancellationToken);

        await dbContext.Database.ExecuteSqlRawAsync(
            """
            CREATE INDEX IF NOT EXISTS "IX_MessageViews_ViewerUserId" ON "MessageViews" ("ViewerUserId");
            """,
            cancellationToken);
    }

    private async Task EnsureChatMembersIndexesAsync(CancellationToken cancellationToken)
    {
        await dbContext.Database.ExecuteSqlRawAsync(
            """
            CREATE INDEX IF NOT EXISTS "IX_ChatMembers_UserId" ON "ChatMembers" ("UserId");
            """,
            cancellationToken);
    }

    private async Task EnsureUserPrivacySettingsTableAsync(CancellationToken cancellationToken)
    {
        await dbContext.Database.ExecuteSqlRawAsync(
            """
            CREATE TABLE IF NOT EXISTS "UserPrivacySettings" (
                "UserId" TEXT NOT NULL CONSTRAINT "PK_UserPrivacySettings" PRIMARY KEY,
                "PhoneNumberVisibility" TEXT NOT NULL DEFAULT 'contacts',
                "LastSeenVisibility" TEXT NOT NULL DEFAULT 'contacts',
                "ProfilePhotoVisibility" TEXT NOT NULL DEFAULT 'everyone',
                "BlockedUserIds" TEXT NOT NULL DEFAULT '',
                "AlwaysAllowUserIds" TEXT NOT NULL DEFAULT '',
                "NeverAllowUserIds" TEXT NOT NULL DEFAULT '',
                "PasscodeLockEnabled" INTEGER NOT NULL DEFAULT 0,
                "ShowMessagePreview" INTEGER NOT NULL DEFAULT 1,
                "UpdatedAt" TEXT NOT NULL,
                CONSTRAINT "FK_UserPrivacySettings_Users_UserId" FOREIGN KEY ("UserId") REFERENCES "Users" ("Id") ON DELETE CASCADE
            );
            """,
            cancellationToken);
    }

    private async Task EnsureChatInviteLinksTableAsync(CancellationToken cancellationToken)
    {
        await dbContext.Database.ExecuteSqlRawAsync(
            """
            CREATE TABLE IF NOT EXISTS "ChatInviteLinks" (
                "Id" TEXT NOT NULL CONSTRAINT "PK_ChatInviteLinks" PRIMARY KEY,
                "ChatId" TEXT NOT NULL,
                "CreatedById" TEXT NOT NULL,
                "Token" TEXT NOT NULL,
                "Title" TEXT NULL,
                "CreatesJoinRequest" INTEGER NOT NULL DEFAULT 0,
                "IsRevoked" INTEGER NOT NULL DEFAULT 0,
                "CreatedAt" TEXT NOT NULL,
                "RevokedAt" TEXT NULL,
                CONSTRAINT "FK_ChatInviteLinks_Chats_ChatId" FOREIGN KEY ("ChatId") REFERENCES "Chats" ("Id") ON DELETE CASCADE,
                CONSTRAINT "FK_ChatInviteLinks_Users_CreatedById" FOREIGN KEY ("CreatedById") REFERENCES "Users" ("Id") ON DELETE RESTRICT
            );
            """,
            cancellationToken);

        await dbContext.Database.ExecuteSqlRawAsync(
            """
            CREATE UNIQUE INDEX IF NOT EXISTS "IX_ChatInviteLinks_Token" ON "ChatInviteLinks" ("Token");
            """,
            cancellationToken);

        await dbContext.Database.ExecuteSqlRawAsync(
            """
            CREATE INDEX IF NOT EXISTS "IX_ChatInviteLinks_ChatId" ON "ChatInviteLinks" ("ChatId");
            """,
            cancellationToken);
    }

    private async Task EnsureChatJoinRequestsTableAsync(CancellationToken cancellationToken)
    {
        await dbContext.Database.ExecuteSqlRawAsync(
            """
            CREATE TABLE IF NOT EXISTS "ChatJoinRequests" (
                "Id" TEXT NOT NULL CONSTRAINT "PK_ChatJoinRequests" PRIMARY KEY,
                "ChatId" TEXT NOT NULL,
                "InviteLinkId" TEXT NOT NULL,
                "UserId" TEXT NOT NULL,
                "Status" INTEGER NOT NULL DEFAULT 0,
                "RequestedAt" TEXT NOT NULL,
                "ResolvedAt" TEXT NULL,
                "ResolvedByUserId" TEXT NULL,
                CONSTRAINT "FK_ChatJoinRequests_Chats_ChatId" FOREIGN KEY ("ChatId") REFERENCES "Chats" ("Id") ON DELETE CASCADE,
                CONSTRAINT "FK_ChatJoinRequests_ChatInviteLinks_InviteLinkId" FOREIGN KEY ("InviteLinkId") REFERENCES "ChatInviteLinks" ("Id") ON DELETE CASCADE,
                CONSTRAINT "FK_ChatJoinRequests_Users_UserId" FOREIGN KEY ("UserId") REFERENCES "Users" ("Id") ON DELETE CASCADE,
                CONSTRAINT "FK_ChatJoinRequests_Users_ResolvedByUserId" FOREIGN KEY ("ResolvedByUserId") REFERENCES "Users" ("Id") ON DELETE SET NULL
            );
            """,
            cancellationToken);

        await dbContext.Database.ExecuteSqlRawAsync(
            """
            CREATE INDEX IF NOT EXISTS "IX_ChatJoinRequests_ChatId_Status" ON "ChatJoinRequests" ("ChatId", "Status");
            """,
            cancellationToken);

        await dbContext.Database.ExecuteSqlRawAsync(
            """
            CREATE UNIQUE INDEX IF NOT EXISTS "IX_ChatJoinRequests_InviteLinkId_UserId" ON "ChatJoinRequests" ("InviteLinkId", "UserId");
            """,
            cancellationToken);
    }

    private async Task EnsureChatTopicsTableAsync(CancellationToken cancellationToken)
    {
        await dbContext.Database.ExecuteSqlRawAsync(
            """
            CREATE TABLE IF NOT EXISTS "ChatTopics" (
                "Id" TEXT NOT NULL CONSTRAINT "PK_ChatTopics" PRIMARY KEY,
                "ChatId" TEXT NOT NULL,
                "CreatedById" TEXT NOT NULL,
                "Title" TEXT NOT NULL,
                "Icon" TEXT NOT NULL DEFAULT '💬',
                "IsPinned" INTEGER NOT NULL DEFAULT 0,
                "IsClosed" INTEGER NOT NULL DEFAULT 0,
                "CreatedAt" TEXT NOT NULL,
                "LastActivityAt" TEXT NOT NULL,
                CONSTRAINT "FK_ChatTopics_Chats_ChatId" FOREIGN KEY ("ChatId") REFERENCES "Chats" ("Id") ON DELETE CASCADE,
                CONSTRAINT "FK_ChatTopics_Users_CreatedById" FOREIGN KEY ("CreatedById") REFERENCES "Users" ("Id") ON DELETE RESTRICT
            );
            """,
            cancellationToken);

        await dbContext.Database.ExecuteSqlRawAsync(
            """
            CREATE INDEX IF NOT EXISTS "IX_ChatTopics_ChatId_IsPinned" ON "ChatTopics" ("ChatId", "IsPinned");
            """,
            cancellationToken);

        await dbContext.Database.ExecuteSqlRawAsync(
            """
            CREATE INDEX IF NOT EXISTS "IX_ChatTopics_ChatId_LastActivityAt" ON "ChatTopics" ("ChatId", "LastActivityAt");
            """,
            cancellationToken);
    }

    private async Task EnsureMessagesPinnedIndexAsync(CancellationToken cancellationToken)
    {
        await dbContext.Database.ExecuteSqlRawAsync(
            """
            CREATE INDEX IF NOT EXISTS "IX_Messages_ChatId_PinnedAt" ON "Messages" ("ChatId", "PinnedAt");
            """,
            cancellationToken);
    }

    private async Task EnsureMessagesChatSenderSentAtIndexAsync(CancellationToken cancellationToken)
    {
        await dbContext.Database.ExecuteSqlRawAsync(
            """
            CREATE INDEX IF NOT EXISTS "IX_Messages_ChatId_SenderId_SentAt" ON "Messages" ("ChatId", "SenderId", "SentAt");
            """,
            cancellationToken);
    }

    private async Task EnsureMessagesAlbumIndexAsync(CancellationToken cancellationToken)
    {
        await dbContext.Database.ExecuteSqlRawAsync(
            """
            CREATE INDEX IF NOT EXISTS "IX_Messages_MediaAlbumId" ON "Messages" ("MediaAlbumId");
            """,
            cancellationToken);
    }

    private async Task EnsureMessagesTopicIndexAsync(CancellationToken cancellationToken)
    {
        await dbContext.Database.ExecuteSqlRawAsync(
            """
            CREATE INDEX IF NOT EXISTS "IX_Messages_TopicId" ON "Messages" ("TopicId");
            """,
            cancellationToken);
    }

    private async Task EnsureMessagesExpiresAtIndexAsync(CancellationToken cancellationToken)
    {
        await dbContext.Database.ExecuteSqlRawAsync(
            """
            CREATE INDEX IF NOT EXISTS "IX_Messages_ExpiresAt" ON "Messages" ("ExpiresAt");
            """,
            cancellationToken);
    }

    private async Task EnsureChatsPublicUsernameIndexAsync(CancellationToken cancellationToken)
    {
        await dbContext.Database.ExecuteSqlRawAsync(
            """
            CREATE UNIQUE INDEX IF NOT EXISTS "IX_Chats_PublicUsername" ON "Chats" ("PublicUsername") WHERE "PublicUsername" IS NOT NULL;
            """,
            cancellationToken);
    }

    private async Task EnsureChatsLinkedDiscussionIndexAsync(CancellationToken cancellationToken)
    {
        await dbContext.Database.ExecuteSqlRawAsync(
            """
            CREATE INDEX IF NOT EXISTS "IX_Chats_LinkedDiscussionChatId" ON "Chats" ("LinkedDiscussionChatId");
            """,
            cancellationToken);
    }

    private async Task EnsureLegacyChannelPostsMarkedAsync(CancellationToken cancellationToken)
    {
        await dbContext.Database.ExecuteSqlRawAsync(
            """
            UPDATE "Messages"
            SET "PostedAsChannel" = 1
            WHERE "PostedAsChannel" = 0
              AND "ChatId" IN (SELECT "Id" FROM "Chats" WHERE "Type" = 3);
            """,
            cancellationToken);
    }

    private async Task EnsureMessageAttachmentsSortIndexAsync(CancellationToken cancellationToken)
    {
        await dbContext.Database.ExecuteSqlRawAsync(
            """
            CREATE INDEX IF NOT EXISTS "IX_MessageAttachments_MessageId_SortOrder" ON "MessageAttachments" ("MessageId", "SortOrder");
            """,
            cancellationToken);
    }

    private async Task EnsureChatMemberStateIndexesAsync(CancellationToken cancellationToken)
    {
        await dbContext.Database.ExecuteSqlRawAsync(
            """
            CREATE INDEX IF NOT EXISTS "IX_ChatMembers_UserId_IsPinned" ON "ChatMembers" ("UserId", "IsPinned");
            """,
            cancellationToken);

        await dbContext.Database.ExecuteSqlRawAsync(
            """
            CREATE INDEX IF NOT EXISTS "IX_ChatMembers_UserId_IsArchived" ON "ChatMembers" ("UserId", "IsArchived");
            """,
            cancellationToken);

        await dbContext.Database.ExecuteSqlRawAsync(
            """
            CREATE INDEX IF NOT EXISTS "IX_ChatMembers_UserId_FolderKey" ON "ChatMembers" ("UserId", "FolderKey");
            """,
            cancellationToken);
    }

    private async Task EnsureUsersPhoneIndexAsync(CancellationToken cancellationToken)
    {
        await dbContext.Database.ExecuteSqlRawAsync(
            """
            CREATE UNIQUE INDEX IF NOT EXISTS "IX_Users_NormalizedPhoneNumber" ON "Users" ("NormalizedPhoneNumber") WHERE "NormalizedPhoneNumber" IS NOT NULL;
            """,
            cancellationToken);
    }

    private async Task EnsureColumnAsync(
        string tableName,
        string columnName,
        string definition,
        CancellationToken cancellationToken)
    {
        var connection = dbContext.Database.GetDbConnection();
        var shouldClose = connection.State != ConnectionState.Open;
        if (shouldClose)
        {
            await connection.OpenAsync(cancellationToken);
        }

        try
        {
            await using var command = connection.CreateCommand();
            command.CommandText = $"PRAGMA table_info(\"{tableName}\");";

            await using var reader = await command.ExecuteReaderAsync(cancellationToken);
            while (await reader.ReadAsync(cancellationToken))
            {
                if (string.Equals(reader.GetString(1), columnName, StringComparison.OrdinalIgnoreCase))
                {
                    return;
                }
            }

            await reader.DisposeAsync();

            await using var alterCommand = connection.CreateCommand();
            alterCommand.CommandText = $"ALTER TABLE \"{tableName}\" ADD COLUMN \"{columnName}\" {definition};";
            await alterCommand.ExecuteNonQueryAsync(cancellationToken);
        }
        finally
        {
            if (shouldClose)
            {
                await connection.CloseAsync();
            }
        }
    }
}
