using System.Security.Claims;
using Ikar.Server.Controllers;
using Ikar.Server.Data;
using Ikar.Server.Infrastructure;
using Ikar.Server.Infrastructure.Bots;
using Ikar.Server.Infrastructure.Auth;
using Ikar.Server.Infrastructure.Hubs;
using Ikar.Server.Infrastructure.Push;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;

namespace Ikar.Server.Tests;

internal sealed class SqliteTestDatabase : IAsyncDisposable
{
    private readonly SqliteConnection _connection = new("Data Source=:memory:");

    public async Task<IkarDbContext> CreateContextAsync()
    {
        await _connection.OpenAsync();
        var options = new DbContextOptionsBuilder<IkarDbContext>()
            .UseSqlite(_connection)
            .Options;
        var dbContext = new IkarDbContext(options);
        await dbContext.Database.EnsureCreatedAsync();
        return dbContext;
    }

    public async ValueTask DisposeAsync()
    {
        await _connection.DisposeAsync();
    }
}

internal static class ControllerTestFixture
{
    public static ChatsController CreateChatsController(IkarDbContext dbContext, Guid userId)
    {
        var controller = new ChatsController(
            dbContext,
            new PresenceTracker(),
            new NoopHubContext<MessengerHub>(),
            attachmentStorageService: null!,
            new PushDispatchQueue(),
            new BotUpdateService(dbContext));
        AttachUser(controller, userId);
        return controller;
    }

    public static UsersController CreateUsersController(IkarDbContext dbContext, Guid userId)
    {
        var controller = new UsersController(
            dbContext,
            new PresenceTracker(),
            avatarStorageService: null!);
        AttachUser(controller, userId);
        return controller;
    }

    public static BotsController CreateBotsController(IkarDbContext dbContext, Guid userId)
    {
        var controller = new BotsController(dbContext, new BotTokenService());
        AttachUser(controller, userId);
        return controller;
    }

    public static StoriesController CreateStoriesController(IkarDbContext dbContext, Guid userId)
    {
        var controller = new StoriesController(
            dbContext,
            new PresenceTracker(),
            attachmentStorageService: null!);
        AttachUser(controller, userId);
        return controller;
    }

    public static T ValueOf<T>(ActionResult<T> actionResult)
    {
        var ok = Assert.IsType<OkObjectResult>(actionResult.Result);
        return Assert.IsAssignableFrom<T>(ok.Value);
    }

    private static void AttachUser(ControllerBase controller, Guid userId)
    {
        var identity = new ClaimsIdentity(
            [new Claim(ClaimTypes.NameIdentifier, userId.ToString())],
            authenticationType: "Test");
        controller.ControllerContext = new ControllerContext
        {
            HttpContext = new DefaultHttpContext
            {
                User = new ClaimsPrincipal(identity)
            }
        };
    }
}

internal sealed class NoopHubContext<THub> : IHubContext<THub>
    where THub : Hub
{
    public IHubClients Clients { get; } = new NoopHubClients();

    public IGroupManager Groups { get; } = new NoopGroupManager();

    private sealed class NoopHubClients : IHubClients
    {
        private static readonly IClientProxy Proxy = new NoopClientProxy();

        public IClientProxy All => Proxy;

        public IClientProxy AllExcept(IReadOnlyList<string> excludedConnectionIds) => Proxy;

        public IClientProxy Client(string connectionId) => Proxy;

        public IClientProxy Clients(IReadOnlyList<string> connectionIds) => Proxy;

        public IClientProxy Group(string groupName) => Proxy;

        public IClientProxy GroupExcept(string groupName, IReadOnlyList<string> excludedConnectionIds) => Proxy;

        public IClientProxy Groups(IReadOnlyList<string> groupNames) => Proxy;

        public IClientProxy User(string userId) => Proxy;

        public IClientProxy Users(IReadOnlyList<string> userIds) => Proxy;
    }

    private sealed class NoopClientProxy : IClientProxy
    {
        public Task SendCoreAsync(string method, object?[] args, CancellationToken cancellationToken = default) =>
            Task.CompletedTask;
    }

    private sealed class NoopGroupManager : IGroupManager
    {
        public Task AddToGroupAsync(string connectionId, string groupName, CancellationToken cancellationToken = default) =>
            Task.CompletedTask;

        public Task RemoveFromGroupAsync(string connectionId, string groupName, CancellationToken cancellationToken = default) =>
            Task.CompletedTask;
    }
}
