using System.Net;
using System.Net.Http.Json;
using System.Text.Json;
using GameList.Tests.Support;
using Microsoft.EntityFrameworkCore;

namespace GameList.Tests;

public class VoteTests
{
    [Fact]
    public async Task Finalizing_votes_blocks_further_changes()
    {
        await using var factory = new TestWebApplicationFactory();
        var client = factory.CreateClientWithCookies();
        await client.RegisterAsync("voter");

        var suggestionId = await client.CreateSuggestionAsync("VoteGame");

        await client.PostAsJsonAsync("/api/me/phase/next", new { });

        var vote = await client.PostAsJsonAsync("/api/votes", new
        {
            SuggestionId = suggestionId,
            Score = 7
        });
        vote.EnsureSuccessStatusCode();

        var finalize = await client.PostAsJsonAsync("/api/votes/finalize", new { Final = true });
        finalize.EnsureSuccessStatusCode();

        var change = await client.PostAsJsonAsync("/api/votes", new
        {
            SuggestionId = suggestionId,
            Score = 5
        });

        Assert.Equal(HttpStatusCode.BadRequest, change.StatusCode);
    }

    [Fact]
    public async Task Score_out_of_range_rejected()
    {
        await using var factory = new TestWebApplicationFactory();
        var client = factory.CreateClientWithCookies();
        await client.RegisterAsync("score");
        var id = await client.CreateSuggestionAsync("RangeGame");
        await client.PostAsJsonAsync("/api/me/phase/next", new { });

        var resp = await client.PostAsJsonAsync("/api/votes", new
        {
            SuggestionId = id,
            Score = 11
        });
        Assert.Equal(HttpStatusCode.BadRequest, resp.StatusCode);
    }

    [Fact]
    public async Task Negative_score_rejected()
    {
        await using var factory = new TestWebApplicationFactory();
        var client = factory.CreateClientWithCookies();
        await client.RegisterAsync("negative");
        var id = await client.CreateSuggestionAsync("RangeGame2");
        await client.PostAsJsonAsync("/api/me/phase/next", new { });

        var resp = await client.PostAsJsonAsync("/api/votes", new
        {
            SuggestionId = id,
            Score = -1
        });
        Assert.Equal(HttpStatusCode.BadRequest, resp.StatusCode);
    }

    [Fact]
    public async Task Invalid_suggestion_id_rejected()
    {
        await using var factory = new TestWebApplicationFactory();
        var client = factory.CreateClientWithCookies();
        await client.RegisterAsync("invalid");
        await client.PostAsJsonAsync("/api/me/phase/next", new { });

        var resp = await client.PostAsJsonAsync("/api/votes", new
        {
            SuggestionId = 9999,
            Score = 5
        });
        Assert.Equal(HttpStatusCode.BadRequest, resp.StatusCode);
    }

    [Fact]
    public async Task Votes_require_display_name()
    {
        await using var factory = new TestWebApplicationFactory();
        var client = factory.CreateClientWithCookies();
        await client.RegisterAsync("anon");
        var id = await client.CreateSuggestionAsync("NeedName");

        await factory.WithDbContextAsync(async db =>
        {
            var p = await db.Players.FirstAsync();
            p.DisplayName = null;
            await db.SaveChangesAsync();
        });

        await client.PostAsJsonAsync("/api/me/phase/next", new { });
        var resp = await client.PostAsJsonAsync("/api/votes", new
        {
            SuggestionId = id,
            Score = 5
        });
        Assert.Equal(HttpStatusCode.BadRequest, resp.StatusCode);
    }

    [Fact]
    public async Task Finalize_only_in_vote_phase()
    {
        await using var factory = new TestWebApplicationFactory();
        var client = factory.CreateClientWithCookies();
        await client.RegisterAsync("phase");

        var resp = await client.PostAsJsonAsync("/api/votes/finalize", new { Final = true });
        Assert.Equal(HttpStatusCode.BadRequest, resp.StatusCode);
    }

    [Fact]
    public async Task Finalize_toggle_allows_unfinalize()
    {
        await using var factory = new TestWebApplicationFactory();
        var client = factory.CreateClientWithCookies();
        await client.RegisterAsync("toggle");
        var id = await client.CreateSuggestionAsync("Toggle");
        await client.PostAsJsonAsync("/api/me/phase/next", new { });
        await client.PostAsJsonAsync("/api/votes", new
        {
            SuggestionId = id,
            Score = 5
        });

        var finalize = await client.PostAsJsonAsync("/api/votes/finalize", new { Final = true });
        finalize.EnsureSuccessStatusCode();
        var unfinalize = await client.PostAsJsonAsync("/api/votes/finalize", new { Final = false });
        unfinalize.EnsureSuccessStatusCode();

        var me = await client.GetFromJsonAsync<JsonElement>("/api/me");
        Assert.False(me.GetProperty("votesFinal").GetBoolean());
    }

    [Fact]
    public async Task Linked_votes_apply_to_all_linked_suggestions()
    {
        await using var factory = new TestWebApplicationFactory();
        var admin = factory.CreateClientWithCookies();
        await admin.RegisterAsync("admin", admin: true);
        await admin.PostAsJsonAsync("/api/me/phase/next", new { });

        var player = factory.CreateClientWithCookies();
        await player.RegisterAsync("linker");

        var id1 = await player.CreateSuggestionAsync("Game A");
        var id2 = await player.CreateSuggestionAsync("Game B");

        await player.PostAsJsonAsync("/api/me/phase/next", new { });

        var linkResponse = await admin.PostAsJsonAsync("/api/admin/link-suggestions", new
        {
            SourceSuggestionId = id1,
            TargetSuggestionId = id2
        });
        linkResponse.EnsureSuccessStatusCode();

        var vote = await player.PostAsJsonAsync("/api/votes", new
        {
            SuggestionId = id1,
            Score = 9
        });
        vote.EnsureSuccessStatusCode();

        var mine = await player.GetFromJsonAsync<List<VoteRecord>>("/api/votes/mine");
        Assert.NotNull(mine);

        Assert.Equal(2, mine.Count);
        Assert.All(mine, v => Assert.Equal(9, v.Score));
    }

    [Fact]
    public async Task Linked_votes_apply_across_chain()
    {
        await using var factory = new TestWebApplicationFactory();
        var admin = factory.CreateClientWithCookies();
        await admin.RegisterAsync("admin", admin: true);
        await admin.PostAsJsonAsync("/api/me/phase/next", new { });

        var player = factory.CreateClientWithCookies();
        await player.RegisterAsync("chain");

        var a = await player.CreateSuggestionAsync("A");
        var b = await player.CreateSuggestionAsync("B");
        var c = await player.CreateSuggestionAsync("C");

        await player.PostAsJsonAsync("/api/me/phase/next", new { });

        await admin.PostAsJsonAsync("/api/admin/link-suggestions", new
        {
            SourceSuggestionId = a,
            TargetSuggestionId = b
        });
        await admin.PostAsJsonAsync("/api/admin/link-suggestions", new
        {
            SourceSuggestionId = b,
            TargetSuggestionId = c
        });

        var vote = await player.PostAsJsonAsync("/api/votes", new
        {
            SuggestionId = c,
            Score = 6
        });
        vote.EnsureSuccessStatusCode();

        var mine = await player.GetFromJsonAsync<List<VoteRecord>>("/api/votes/mine");
        Assert.NotNull(mine);
        Assert.Equal(3, mine.Count);
        Assert.All(mine, v => Assert.Equal(6, v.Score));
    }

    [Fact]
    public async Task Votes_mine_requires_vote_phase_and_auth()
    {
        await using var factory = new TestWebApplicationFactory();
        var anon = factory.CreateClient();
        var unauth = await anon.GetAsync("/api/votes/mine");
        Assert.Equal(HttpStatusCode.Unauthorized, unauth.StatusCode);

        var client = factory.CreateClientWithCookies();
        await client.RegisterAsync("phaseguard");
        var resp = await client.GetAsync("/api/votes/mine");
        Assert.Equal(HttpStatusCode.BadRequest, resp.StatusCode);
    }

    // ReSharper disable once NotAccessedPositionalProperty.Local
    // ReSharper disable once ClassNeverInstantiated.Local
    private record VoteRecord(int SuggestionId, int Score);
}
