using System.Security.Claims;
using GameList.Data;
using GameList.Domain;
using GameList.Infrastructure;
using GameList.Tests.Support;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;

namespace GameList.Tests;

public class FiltersTests
{
    [Fact]
    public async Task Admin_only_filter_blocks_non_admin()
    {
        await using var factory = new TestWebApplicationFactory();
        var client = factory.CreateClientWithCookies();
        await client.RegisterAsync("user");

        var ctx = await BuildContextAsync(factory, isAdmin: false);
        var filter = new AdminOnlyFilter();
        var result = await filter.InvokeAsync(ctx, _ => ValueTask.FromResult<object?>(Results.Ok()));
        await AssertStatusAsync(result, StatusCodes.Status401Unauthorized);
    }

    [Fact]
    public async Task Phase_requirement_allows_admin_override_when_enabled()
    {
        await using var factory = new TestWebApplicationFactory();
        var ctx = await BuildContextAsync(factory, isAdmin: true, phase: Phase.Suggest);
        var filter = new PhaseRequirementFilter(Phase.Vote, allowAdminOverride: true);
        var called = false;
        var result = await filter.InvokeAsync(ctx, _ =>
        {
            called = true;
            return ValueTask.FromResult<object?>(Results.Ok());
        });

        Assert.True(called);
        await AssertStatusAsync(result, StatusCodes.Status200OK);
    }

    [Fact]
    public async Task Phase_or_joker_filter_blocks_without_joker_in_vote()
    {
        await using var factory = new TestWebApplicationFactory();
        var ctx = await BuildContextAsync(factory, isAdmin: false, phase: Phase.Vote, hasJoker: false);
        var filter = new PhaseOrJokerFilter();
        var result = await filter.InvokeAsync(ctx, _ => ValueTask.FromResult<object?>(Results.Ok()));
        await AssertStatusAsync(result, StatusCodes.Status400BadRequest);
    }

    [Fact]
    public async Task Phase_requirement_uses_effective_phase_without_mutating_player()
    {
        await using var factory = new TestWebApplicationFactory();
        var ctx = await BuildContextAsync(factory, isAdmin: false, phase: Phase.Vote, resultsOpen: true);
        var filter = new PhaseRequirementFilter(Phase.Results);
        var called = false;

        var result = await filter.InvokeAsync(ctx, _ =>
        {
            called = true;
            return ValueTask.FromResult<object?>(Results.Ok());
        });

        Assert.True(called);
        await AssertStatusAsync(result, StatusCodes.Status200OK);

        var playerId = Guid.Parse(ctx.HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier)!);
        var db = ctx.HttpContext.RequestServices.GetRequiredService<AppDbContext>();
        var storedPlayer = await db.Players.AsNoTracking().SingleAsync(p => p.Id == playerId);
        Assert.Equal(Phase.Vote, storedPlayer.CurrentPhase);
    }

    private static async Task<TestInvocationContext> BuildContextAsync(TestWebApplicationFactory factory, bool isAdmin, Phase phase = Phase.Vote, bool hasJoker = false, bool resultsOpen = false)
    {
        var scope = factory.Services.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
        var player = new Player
        {
            Id = Guid.NewGuid(),
            Username = $"user-{Guid.NewGuid():N}",
            NormalizedUsername = $"user-{Guid.NewGuid():N}",
            PasswordHash = [1],
            PasswordSalt = [1],
            IsAdmin = isAdmin,
            CurrentPhase = phase,
            HasJoker = hasJoker,
            DisplayName = "User"
        };
        db.Players.Add(player);
        var state = await db.AppState.FirstAsync();
        state.ResultsOpen = resultsOpen;
        await db.SaveChangesAsync();

        var ctx = new DefaultHttpContext
        {
            RequestServices = scope.ServiceProvider,
            User = new ClaimsPrincipal(new ClaimsIdentity([
                new Claim(ClaimTypes.NameIdentifier, player.Id.ToString()),
                new Claim(ClaimTypes.Name, player.Username),
                new Claim(PlayerIdentityExtensions.AdminClaim, isAdmin ? "true" : "false")
            ], "cookie"))
        };

        return new TestInvocationContext(ctx);
    }

    private class TestInvocationContext(DefaultHttpContext context) : EndpointFilterInvocationContext
    {
        public override HttpContext HttpContext => context;
        public override object?[] Arguments => [];
        public override T GetArgument<T>(int index) => throw new NotImplementedException();
    }

    private static async Task AssertStatusAsync(object? result, int statusCode)
    {
        var http = new DefaultHttpContext
        {
            RequestServices = new ServiceCollection().AddLogging().BuildServiceProvider(),
            Response = { Body = new MemoryStream() }
        };
        await ((IResult)result!).ExecuteAsync(http);
        Assert.Equal(statusCode, http.Response.StatusCode);
    }
}
