using Ikar.Server.Controllers;
using Ikar.Server.Data;
using Ikar.Server.Data.Entities;
using Ikar.Server.Infrastructure;
using Ikar.Server.Infrastructure.Auth;
using Ikar.Server.Infrastructure.Bots;
using Ikar.Server.Infrastructure.Hubs;
using Ikar.Server.Infrastructure.Push;
using Ikar.Server.Infrastructure.Storage;
using Ikar.Shared;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.FileProviders;

namespace Ikar.Server.Tests;

public sealed class BotApiControllerTests
{
    [Fact]
    public async Task SendDocument_DecodesPercentEncodedFileName()
    {
        var tempRoot = Path.Combine(Path.GetTempPath(), $"ikar-bot-api-tests-{Guid.NewGuid():N}");
        Directory.CreateDirectory(tempRoot);
        try
        {
            await using var database = new SqliteTestDatabase();
            await using var dbContext = await database.CreateContextAsync();

            var owner = CreateUser("owner");
            var botUser = CreateUser("documentbot", isBot: true);
            var tokenService = new BotTokenService();
            var token = "bot-token";
            var bot = new Bot
            {
                Id = Guid.NewGuid(),
                UserId = botUser.Id,
                User = botUser,
                OwnerUserId = owner.Id,
                OwnerUser = owner,
                TokenHash = tokenService.HashToken(token)
            };
            var chat = CreateDirectChat(owner, botUser);

            dbContext.AddRange(owner, botUser, bot, chat);
            await dbContext.SaveChangesAsync();

            await using var stream = new MemoryStream("%PDF-1.3"u8.ToArray());
            var encodedFileName = "Fluke_8508A_" + Uri.EscapeDataString("методика.pdf");
            var file = new FormFile(stream, 0, stream.Length, "file", encodedFileName)
            {
                Headers = new HeaderDictionary(),
                ContentType = "application/pdf"
            };
            var controller = CreateController(dbContext, tokenService, tempRoot);

            var response = await controller.SendDocument(
                token,
                new BotSendAttachmentRequest
                {
                    ChatId = chat.Id,
                    Text = "",
                    File = file
                },
                CancellationToken.None);

            var message = ControllerTestFixture.ValueOf(response);
            var attachment = Assert.Single(message.Attachments);
            Assert.Equal("Fluke_8508A_методика.pdf", attachment.FileName);
            Assert.Equal("application/pdf", attachment.ContentType);
        }
        finally
        {
            if (Directory.Exists(tempRoot))
            {
                Directory.Delete(tempRoot, recursive: true);
            }
        }
    }

    [Fact]
    public async Task SendDocument_SavesOggAsVoiceNoteAttachment()
    {
        var tempRoot = Path.Combine(Path.GetTempPath(), $"ikar-bot-api-tests-{Guid.NewGuid():N}");
        Directory.CreateDirectory(tempRoot);
        try
        {
            await using var database = new SqliteTestDatabase();
            await using var dbContext = await database.CreateContextAsync();

            var owner = CreateUser("owner");
            var botUser = CreateUser("documentbot", isBot: true);
            var tokenService = new BotTokenService();
            var token = "bot-token";
            var bot = new Bot
            {
                Id = Guid.NewGuid(),
                UserId = botUser.Id,
                User = botUser,
                OwnerUserId = owner.Id,
                OwnerUser = owner,
                TokenHash = tokenService.HashToken(token)
            };
            var chat = CreateDirectChat(owner, botUser);

            dbContext.AddRange(owner, botUser, bot, chat);
            await dbContext.SaveChangesAsync();

            await using var stream = new MemoryStream([1, 2, 3, 4]);
            var file = new FormFile(stream, 0, stream.Length, "file", "song.ogg")
            {
                Headers = new HeaderDictionary(),
                ContentType = "audio/ogg"
            };
            var controller = CreateController(dbContext, tokenService, tempRoot);

            var response = await controller.SendDocument(
                token,
                new BotSendAttachmentRequest
                {
                    ChatId = chat.Id,
                    Text = "",
                    File = file
                },
                CancellationToken.None);

            var message = ControllerTestFixture.ValueOf(response);
            var attachment = Assert.Single(message.Attachments);
            Assert.Equal(AttachmentKind.VoiceNote, attachment.Kind);
            Assert.Equal("audio/ogg", attachment.ContentType);
        }
        finally
        {
            if (Directory.Exists(tempRoot))
            {
                Directory.Delete(tempRoot, recursive: true);
            }
        }
    }

    [Fact]
    public async Task SendPhoto_SavesImageAttachment()
    {
        var tempRoot = Path.Combine(Path.GetTempPath(), $"ikar-bot-api-tests-{Guid.NewGuid():N}");
        Directory.CreateDirectory(tempRoot);
        try
        {
            await using var database = new SqliteTestDatabase();
            await using var dbContext = await database.CreateContextAsync();

            var owner = CreateUser("owner");
            var botUser = CreateUser("photobot", isBot: true);
            var tokenService = new BotTokenService();
            var token = "bot-token";
            var bot = new Bot
            {
                Id = Guid.NewGuid(),
                UserId = botUser.Id,
                User = botUser,
                OwnerUserId = owner.Id,
                OwnerUser = owner,
                TokenHash = tokenService.HashToken(token)
            };
            var chat = CreateDirectChat(owner, botUser);

            dbContext.AddRange(owner, botUser, bot, chat);
            await dbContext.SaveChangesAsync();

            await using var stream = new MemoryStream([0xFF, 0xD8, 0xFF, 0xD9]);
            var file = new FormFile(stream, 0, stream.Length, "file", "photo.jpg")
            {
                Headers = new HeaderDictionary(),
                ContentType = "image/jpeg"
            };
            var controller = CreateController(dbContext, tokenService, tempRoot);

            var response = await controller.SendPhoto(
                token,
                new BotSendAttachmentRequest
                {
                    ChatId = chat.Id,
                    Text = "caption",
                    File = file
                },
                CancellationToken.None);

            var message = ControllerTestFixture.ValueOf(response);
            Assert.Equal("caption", message.Text);
            var attachment = Assert.Single(message.Attachments);
            Assert.Equal(AttachmentKind.File, attachment.Kind);
            Assert.Equal("image/jpeg", attachment.ContentType);
            Assert.Equal("photo.jpg", attachment.FileName);
        }
        finally
        {
            if (Directory.Exists(tempRoot))
            {
                Directory.Delete(tempRoot, recursive: true);
            }
        }
    }

    [Fact]
    public async Task SendPhoto_RejectsVideoAttachment()
    {
        var tempRoot = Path.Combine(Path.GetTempPath(), $"ikar-bot-api-tests-{Guid.NewGuid():N}");
        Directory.CreateDirectory(tempRoot);
        try
        {
            await using var database = new SqliteTestDatabase();
            await using var dbContext = await database.CreateContextAsync();

            var owner = CreateUser("owner");
            var botUser = CreateUser("photobot", isBot: true);
            var tokenService = new BotTokenService();
            var token = "bot-token";
            var bot = new Bot
            {
                Id = Guid.NewGuid(),
                UserId = botUser.Id,
                User = botUser,
                OwnerUserId = owner.Id,
                OwnerUser = owner,
                TokenHash = tokenService.HashToken(token)
            };
            var chat = CreateDirectChat(owner, botUser);

            dbContext.AddRange(owner, botUser, bot, chat);
            await dbContext.SaveChangesAsync();

            await using var stream = new MemoryStream([1, 2, 3, 4]);
            var file = new FormFile(stream, 0, stream.Length, "file", "clip.mp4")
            {
                Headers = new HeaderDictionary(),
                ContentType = "video/mp4"
            };
            var controller = CreateController(dbContext, tokenService, tempRoot);

            var response = await controller.SendPhoto(
                token,
                new BotSendAttachmentRequest
                {
                    ChatId = chat.Id,
                    Text = "",
                    File = file
                },
                CancellationToken.None);

            var badRequest = Assert.IsType<Microsoft.AspNetCore.Mvc.BadRequestObjectResult>(response.Result);
            Assert.Equal("sendPhoto accepts only image files.", badRequest.Value);
        }
        finally
        {
            if (Directory.Exists(tempRoot))
            {
                Directory.Delete(tempRoot, recursive: true);
            }
        }
    }

    [Fact]
    public async Task SendVideo_SavesVideoAttachment()
    {
        var tempRoot = Path.Combine(Path.GetTempPath(), $"ikar-bot-api-tests-{Guid.NewGuid():N}");
        Directory.CreateDirectory(tempRoot);
        try
        {
            await using var database = new SqliteTestDatabase();
            await using var dbContext = await database.CreateContextAsync();

            var owner = CreateUser("owner");
            var botUser = CreateUser("videobot", isBot: true);
            var tokenService = new BotTokenService();
            var token = "bot-token";
            var bot = new Bot
            {
                Id = Guid.NewGuid(),
                UserId = botUser.Id,
                User = botUser,
                OwnerUserId = owner.Id,
                OwnerUser = owner,
                TokenHash = tokenService.HashToken(token)
            };
            var chat = CreateDirectChat(owner, botUser);

            dbContext.AddRange(owner, botUser, bot, chat);
            await dbContext.SaveChangesAsync();

            await using var stream = new MemoryStream([0, 0, 0, 24, 102, 116, 121, 112]);
            var file = new FormFile(stream, 0, stream.Length, "file", "clip.mp4")
            {
                Headers = new HeaderDictionary(),
                ContentType = "video/mp4"
            };
            var controller = CreateController(dbContext, tokenService, tempRoot);

            var response = await controller.SendVideo(
                token,
                new BotSendAttachmentRequest
                {
                    ChatId = chat.Id,
                    Text = "clip",
                    File = file
                },
                CancellationToken.None);

            var message = ControllerTestFixture.ValueOf(response);
            Assert.Equal("clip", message.Text);
            var attachment = Assert.Single(message.Attachments);
            Assert.Equal(AttachmentKind.File, attachment.Kind);
            Assert.Equal("video/mp4", attachment.ContentType);
            Assert.Equal("clip.mp4", attachment.FileName);
        }
        finally
        {
            if (Directory.Exists(tempRoot))
            {
                Directory.Delete(tempRoot, recursive: true);
            }
        }
    }

    [Fact]
    public async Task SendMediaGroup_SavesImageAndVideoAlbum()
    {
        var tempRoot = Path.Combine(Path.GetTempPath(), $"ikar-bot-api-tests-{Guid.NewGuid():N}");
        Directory.CreateDirectory(tempRoot);
        try
        {
            await using var database = new SqliteTestDatabase();
            await using var dbContext = await database.CreateContextAsync();

            var owner = CreateUser("owner");
            var botUser = CreateUser("albumbot", isBot: true);
            var tokenService = new BotTokenService();
            var token = "bot-token";
            var bot = new Bot
            {
                Id = Guid.NewGuid(),
                UserId = botUser.Id,
                User = botUser,
                OwnerUserId = owner.Id,
                OwnerUser = owner,
                TokenHash = tokenService.HashToken(token)
            };
            var chat = CreateDirectChat(owner, botUser);

            dbContext.AddRange(owner, botUser, bot, chat);
            await dbContext.SaveChangesAsync();

            var photo = CreateFormFile([0xFF, 0xD8, 0xFF, 0xD9], "photo.jpg", "image/jpeg");
            var video = CreateFormFile([0, 0, 0, 24, 102, 116, 121, 112], "clip.mp4", "video/mp4");
            var controller = CreateController(dbContext, tokenService, tempRoot);

            var response = await controller.SendMediaGroup(
                token,
                new BotSendMediaGroupRequest
                {
                    ChatId = chat.Id,
                    Text = "album",
                    Files = [photo, video]
                },
                CancellationToken.None);

            var message = ControllerTestFixture.ValueOf(response);
            Assert.Equal("album", message.Text);
            Assert.NotNull(message.MediaAlbumId);
            Assert.Equal(2, message.Attachments.Count);
            Assert.Equal("photo.jpg", message.Attachments[0].FileName);
            Assert.Equal(0, message.Attachments[0].SortOrder);
            Assert.Equal("clip.mp4", message.Attachments[1].FileName);
            Assert.Equal(1, message.Attachments[1].SortOrder);
        }
        finally
        {
            if (Directory.Exists(tempRoot))
            {
                Directory.Delete(tempRoot, recursive: true);
            }
        }
    }

    [Fact]
    public async Task SendMediaGroup_RejectsNonMediaAttachment()
    {
        var tempRoot = Path.Combine(Path.GetTempPath(), $"ikar-bot-api-tests-{Guid.NewGuid():N}");
        Directory.CreateDirectory(tempRoot);
        try
        {
            await using var database = new SqliteTestDatabase();
            await using var dbContext = await database.CreateContextAsync();

            var owner = CreateUser("owner");
            var botUser = CreateUser("albumbot", isBot: true);
            var tokenService = new BotTokenService();
            var token = "bot-token";
            var bot = new Bot
            {
                Id = Guid.NewGuid(),
                UserId = botUser.Id,
                User = botUser,
                OwnerUserId = owner.Id,
                OwnerUser = owner,
                TokenHash = tokenService.HashToken(token)
            };
            var chat = CreateDirectChat(owner, botUser);

            dbContext.AddRange(owner, botUser, bot, chat);
            await dbContext.SaveChangesAsync();

            var photo = CreateFormFile([0xFF, 0xD8, 0xFF, 0xD9], "photo.jpg", "image/jpeg");
            var document = CreateFormFile([0x25, 0x50, 0x44, 0x46], "manual.pdf", "application/pdf");
            var controller = CreateController(dbContext, tokenService, tempRoot);

            var response = await controller.SendMediaGroup(
                token,
                new BotSendMediaGroupRequest
                {
                    ChatId = chat.Id,
                    Files = [photo, document]
                },
                CancellationToken.None);

            var badRequest = Assert.IsType<Microsoft.AspNetCore.Mvc.BadRequestObjectResult>(response.Result);
            Assert.Equal("sendMediaGroup accepts only image and video files.", badRequest.Value);
        }
        finally
        {
            if (Directory.Exists(tempRoot))
            {
                Directory.Delete(tempRoot, recursive: true);
            }
        }
    }

    [Fact]
    public async Task SendVoice_SavesOctetStreamOggNameAsVoiceNoteAttachment()
    {
        var tempRoot = Path.Combine(Path.GetTempPath(), $"ikar-bot-api-tests-{Guid.NewGuid():N}");
        Directory.CreateDirectory(tempRoot);
        try
        {
            await using var database = new SqliteTestDatabase();
            await using var dbContext = await database.CreateContextAsync();

            var owner = CreateUser("owner");
            var botUser = CreateUser("voicebot", isBot: true);
            var tokenService = new BotTokenService();
            var token = "bot-token";
            var bot = new Bot
            {
                Id = Guid.NewGuid(),
                UserId = botUser.Id,
                User = botUser,
                OwnerUserId = owner.Id,
                OwnerUser = owner,
                TokenHash = tokenService.HashToken(token)
            };
            var chat = CreateDirectChat(owner, botUser);

            dbContext.AddRange(owner, botUser, bot, chat);
            await dbContext.SaveChangesAsync();

            await using var stream = new MemoryStream([1, 2, 3, 4, 5]);
            var file = new FormFile(stream, 0, stream.Length, "file", "Правила ogg")
            {
                Headers = new HeaderDictionary(),
                ContentType = "application/octet-stream"
            };
            var controller = CreateController(dbContext, tokenService, tempRoot);

            var response = await controller.SendVoice(
                token,
                new BotSendAttachmentRequest
                {
                    ChatId = chat.Id,
                    Text = "",
                    File = file
                },
                CancellationToken.None);

            var message = ControllerTestFixture.ValueOf(response);
            var attachment = Assert.Single(message.Attachments);
            Assert.Equal(AttachmentKind.VoiceNote, attachment.Kind);
            Assert.Equal("audio/ogg", attachment.ContentType);
        }
        finally
        {
            if (Directory.Exists(tempRoot))
            {
                Directory.Delete(tempRoot, recursive: true);
            }
        }
    }

    [Fact]
    public async Task SendMessage_ChannelWithLinkedDiscussion_CreatesDiscussionRoot()
    {
        var tempRoot = Path.Combine(Path.GetTempPath(), $"ikar-bot-api-tests-{Guid.NewGuid():N}");
        Directory.CreateDirectory(tempRoot);
        try
        {
            await using var database = new SqliteTestDatabase();
            await using var dbContext = await database.CreateContextAsync();

            var owner = CreateUser("owner");
            var botUser = CreateUser("newsbot", isBot: true);
            var tokenService = new BotTokenService();
            var token = "bot-token";
            var bot = new Bot
            {
                Id = Guid.NewGuid(),
                UserId = botUser.Id,
                User = botUser,
                OwnerUserId = owner.Id,
                OwnerUser = owner,
                TokenHash = tokenService.HashToken(token)
            };
            var group = CreateGroup(owner, botUser, canBotPost: true);
            var channel = CreateChannel(owner, botUser, group);

            dbContext.AddRange(owner, botUser, bot, group, channel);
            await dbContext.SaveChangesAsync();

            var controller = CreateController(dbContext, tokenService, tempRoot);
            var response = await controller.SendMessage(
                token,
                new BotSendMessageRequest(channel.Id, "Bot channel post"),
                CancellationToken.None);

            var postDto = ControllerTestFixture.ValueOf(response);
            Assert.NotNull(postDto.DiscussionMessageId);

            var discussionRoot = dbContext.Messages.Single(x => x.Id == postDto.DiscussionMessageId);
            Assert.Equal(group.Id, discussionRoot.ChatId);
            Assert.Equal("Bot channel post", discussionRoot.Text);
            Assert.Equal(channel.Title, discussionRoot.ForwardedFromDisplayName);
        }
        finally
        {
            if (Directory.Exists(tempRoot))
            {
                Directory.Delete(tempRoot, recursive: true);
            }
        }
    }

    [Fact]
    public async Task SendVoice_ChannelWithLinkedDiscussion_MirrorsVoiceAttachmentToDiscussionRoot()
    {
        var tempRoot = Path.Combine(Path.GetTempPath(), $"ikar-bot-api-tests-{Guid.NewGuid():N}");
        Directory.CreateDirectory(tempRoot);
        try
        {
            await using var database = new SqliteTestDatabase();
            await using var dbContext = await database.CreateContextAsync();

            var owner = CreateUser("owner");
            var botUser = CreateUser("voicebot", isBot: true);
            var tokenService = new BotTokenService();
            var token = "bot-token";
            var bot = new Bot
            {
                Id = Guid.NewGuid(),
                UserId = botUser.Id,
                User = botUser,
                OwnerUserId = owner.Id,
                OwnerUser = owner,
                TokenHash = tokenService.HashToken(token)
            };
            var group = CreateGroup(owner, botUser, canBotPost: true);
            var channel = CreateChannel(owner, botUser, group);

            dbContext.AddRange(owner, botUser, bot, group, channel);
            await dbContext.SaveChangesAsync();

            var file = CreateFormFile([1, 2, 3, 4], "rules.ogg", "application/octet-stream");
            var controller = CreateController(dbContext, tokenService, tempRoot);
            var response = await controller.SendVoice(
                token,
                new BotSendAttachmentRequest
                {
                    ChatId = channel.Id,
                    Text = "Rules",
                    File = file
                },
                CancellationToken.None);

            var postDto = ControllerTestFixture.ValueOf(response);
            Assert.NotNull(postDto.DiscussionMessageId);

            var discussionRoot = dbContext.Messages
                .Include(x => x.Attachments)
                .Single(x => x.Id == postDto.DiscussionMessageId);
            var mirroredAttachment = Assert.Single(discussionRoot.Attachments);
            Assert.Equal(group.Id, discussionRoot.ChatId);
            Assert.Equal("Rules", discussionRoot.Text);
            Assert.Equal(AttachmentKind.VoiceNote, mirroredAttachment.Kind);
            Assert.Equal("audio/ogg", mirroredAttachment.ContentType);
            Assert.Equal("rules.ogg", mirroredAttachment.OriginalFileName);
            Assert.True(File.Exists(mirroredAttachment.StoragePath));
        }
        finally
        {
            if (Directory.Exists(tempRoot))
            {
                Directory.Delete(tempRoot, recursive: true);
            }
        }
    }

    [Fact]
    public async Task SendMessage_DeniesRestrictedGroupBot()
    {
        var tempRoot = Path.Combine(Path.GetTempPath(), $"ikar-bot-api-tests-{Guid.NewGuid():N}");
        Directory.CreateDirectory(tempRoot);
        try
        {
            await using var database = new SqliteTestDatabase();
            await using var dbContext = await database.CreateContextAsync();

            var owner = CreateUser("owner");
            var botUser = CreateUser("restrictedbot", isBot: true);
            var tokenService = new BotTokenService();
            var token = "bot-token";
            var bot = new Bot
            {
                Id = Guid.NewGuid(),
                UserId = botUser.Id,
                User = botUser,
                OwnerUserId = owner.Id,
                OwnerUser = owner,
                TokenHash = tokenService.HashToken(token)
            };
            var group = CreateGroup(owner, botUser, canBotPost: false);

            dbContext.AddRange(owner, botUser, bot, group);
            await dbContext.SaveChangesAsync();

            var controller = CreateController(dbContext, tokenService, tempRoot);

            var response = await controller.SendMessage(
                token,
                new BotSendMessageRequest(group.Id, "test"),
                CancellationToken.None);

            var forbidden = Assert.IsType<Microsoft.AspNetCore.Mvc.ObjectResult>(response.Result);
            Assert.Equal(StatusCodes.Status403Forbidden, forbidden.StatusCode);
        }
        finally
        {
            if (Directory.Exists(tempRoot))
            {
                Directory.Delete(tempRoot, recursive: true);
            }
        }
    }

    private static BotApiController CreateController(
        IkarDbContext dbContext,
        BotTokenService tokenService,
        string contentRootPath) =>
        new(
            dbContext,
            new PresenceTracker(),
            tokenService,
            new BotUpdateService(dbContext),
            new NoopHubContext<MessengerHub>(),
            new PushDispatchQueue(),
            new AttachmentStorageService(new TestWebHostEnvironment(contentRootPath)));

    private static FormFile CreateFormFile(byte[] content, string fileName, string contentType)
    {
        var stream = new MemoryStream(content);
        return new FormFile(stream, 0, stream.Length, "file", fileName)
        {
            Headers = new HeaderDictionary(),
            ContentType = contentType
        };
    }

    private static User CreateUser(string username, bool isBot = false) =>
        new()
        {
            Id = Guid.NewGuid(),
            Username = username,
            NormalizedUsername = username.ToUpperInvariant(),
            DisplayName = username,
            PasswordHash = "test",
            IsBot = isBot
        };

    private static Chat CreateChannel(User owner, User botUser, Chat linkedDiscussionChat)
    {
        var channel = new Chat
        {
            Id = Guid.NewGuid(),
            Type = ChatType.Channel,
            Title = "News",
            CreatedById = owner.Id,
            CreatedBy = owner,
            LinkedDiscussionChatId = linkedDiscussionChat.Id,
            CreatedAt = new DateTimeOffset(2026, 5, 21, 12, 0, 0, TimeSpan.Zero)
        };
        channel.Members.Add(new ChatMember
        {
            ChatId = channel.Id,
            Chat = channel,
            UserId = owner.Id,
            User = owner,
            IsOwner = true,
            Role = ChatMemberRole.Owner,
            CanPostMessages = true,
            CanDeleteMessages = true,
            CanInviteUsers = true,
            CanPinMessages = true
        });
        channel.Members.Add(new ChatMember
        {
            ChatId = channel.Id,
            Chat = channel,
            UserId = botUser.Id,
            User = botUser,
            Role = ChatMemberRole.Admin,
            CanPostMessages = true
        });
        return channel;
    }

    private static Chat CreateDirectChat(User first, User second)
    {
        var chat = new Chat
        {
            Id = Guid.NewGuid(),
            Type = ChatType.Direct,
            CreatedById = first.Id,
            CreatedBy = first,
            CreatedAt = new DateTimeOffset(2026, 5, 21, 12, 0, 0, TimeSpan.Zero)
        };
        chat.Members.Add(new ChatMember { ChatId = chat.Id, Chat = chat, UserId = first.Id, User = first });
        chat.Members.Add(new ChatMember { ChatId = chat.Id, Chat = chat, UserId = second.Id, User = second });
        return chat;
    }

    private static Chat CreateGroup(User owner, User botUser, bool canBotPost)
    {
        var chat = new Chat
        {
            Id = Guid.NewGuid(),
            Type = ChatType.Group,
            Title = "Group",
            CreatedById = owner.Id,
            CreatedBy = owner,
            CreatedAt = new DateTimeOffset(2026, 5, 21, 12, 0, 0, TimeSpan.Zero)
        };
        chat.Members.Add(new ChatMember
        {
            ChatId = chat.Id,
            Chat = chat,
            UserId = owner.Id,
            User = owner,
            IsOwner = true,
            Role = ChatMemberRole.Owner,
            CanPostMessages = true,
            CanDeleteMessages = true,
            CanInviteUsers = true,
            CanPinMessages = true
        });
        chat.Members.Add(new ChatMember
        {
            ChatId = chat.Id,
            Chat = chat,
            UserId = botUser.Id,
            User = botUser,
            Role = ChatMemberRole.Member,
            CanPostMessages = canBotPost
        });
        return chat;
    }

    private sealed class TestWebHostEnvironment(string contentRootPath) : IWebHostEnvironment
    {
        public string ApplicationName { get; set; } = "Ikar.Server.Tests";
        public IFileProvider WebRootFileProvider { get; set; } = new NullFileProvider();
        public string WebRootPath { get; set; } = contentRootPath;
        public string EnvironmentName { get; set; } = "Development";
        public string ContentRootPath { get; set; } = contentRootPath;
        public IFileProvider ContentRootFileProvider { get; set; } = new PhysicalFileProvider(contentRootPath);
    }
}
