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

namespace GameList.Tests;

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

        var p1 = factory.CreateClientWithCookies();
        await p1.RegisterAsync("alice");
        var p2 = factory.CreateClientWithCookies();
        await p2.RegisterAsync("bob");
        await p2.PostAsJsonAsync("/api/me/phase/next", new { });

        var s1 = await p1.CreateSuggestionAsync("A");
        await p1.PostAsJsonAsync("/api/me/phase/next", new { });
        await p1.PostAsJsonAsync("/api/votes", new
        {
            SuggestionId = s1,
            Score = 5
        });
        await p1.PostAsJsonAsync("/api/votes/finalize", new { Final = true });
        await p2.PostAsJsonAsync("/api/votes", new
        {
            SuggestionId = s1,
            Score = 7
        });
        await p2.PostAsJsonAsync("/api/votes/finalize", new { Final = true });
        await admin.PostAsJsonAsync("/api/votes/finalize", new { Final = true });

        var status = await admin.GetFromJsonAsync<JsonElement>("/api/admin/vote-status");

        Assert.True(status.GetProperty("ready").GetBoolean());
        Assert.Equal(0, status.GetProperty("waiting").GetArrayLength());
    }

    [Fact]
    public async Task Grant_joker_only_in_vote_phase()
    {
        await using var factory = new TestWebApplicationFactory();
        var admin = factory.CreateClientWithCookies();
        await admin.RegisterAsync("admin", admin: true);

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

        var give = await admin.PostAsJsonAsync("/api/admin/joker", new { playerId = (await p.GetProfileIdAsync()) });
        Assert.Equal(HttpStatusCode.BadRequest, give.StatusCode);
    }

    [Fact]
    public async Task Delete_player_cascades_suggestions_and_votes()
    {
        await using var factory = new TestWebApplicationFactory();
        var admin = factory.CreateClientWithCookies();
        await admin.RegisterAsync("admin", admin: true);

        var player = factory.CreateClientWithCookies();
        await player.RegisterAsync("deleteme");
        var suggestionId = await player.CreateSuggestionAsync("DeleteGame");

        await player.PostAsJsonAsync("/api/me/phase/next", new { });
        await player.PostAsJsonAsync("/api/votes", new
        {
            SuggestionId = suggestionId,
            Score = 8
        });

        var resp = await admin.DeleteAsync($"/api/admin/players/{await player.GetProfileIdAsync()}");
        resp.EnsureSuccessStatusCode();

        await factory.WithDbContextAsync(db =>
        {
            try
            {
                Assert.Single(db.Players); // admin remains

                Assert.Empty(db.Suggestions);

                Assert.Empty(db.Votes);
                return Task.CompletedTask;
            }
            catch (Exception exception)
            {
                return Task.FromException(exception);
            }
        });
    }

    [Fact]
    public async Task Link_suggestions_errors_on_same_id_and_already_linked()
    {
        await using var factory = new TestWebApplicationFactory();
        var admin = factory.CreateClientWithCookies();
        await admin.RegisterAsync("admin", admin: true);
        var player = factory.CreateClientWithCookies();
        await player.RegisterAsync("linker");

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

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

        var same = await admin.PostAsJsonAsync("/api/admin/link-suggestions", new
        {
            SourceSuggestionId = a,
            TargetSuggestionId = a
        });
        Assert.Equal(HttpStatusCode.BadRequest, same.StatusCode);

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

        var already = await admin.PostAsJsonAsync("/api/admin/link-suggestions", new
        {
            SourceSuggestionId = a,
            TargetSuggestionId = b
        });
        Assert.Equal(HttpStatusCode.BadRequest, already.StatusCode);
    }

    [Fact]
    public async Task Unlink_suggestions_clears_group_votes()
    {
        await using var factory = new TestWebApplicationFactory();
        var admin = factory.CreateClientWithCookies();
        await admin.RegisterAsync("admin", admin: true);
        var player = factory.CreateClientWithCookies();
        await player.RegisterAsync("unlinker");

        var a = await player.CreateSuggestionAsync("Game A");
        var b = await player.CreateSuggestionAsync("Game B");
        await player.PostAsJsonAsync("/api/me/phase/next", new { });
        await admin.PostAsJsonAsync("/api/me/phase/next", new { });
        await admin.PostAsJsonAsync("/api/admin/link-suggestions", new
        {
            SourceSuggestionId = a,
            TargetSuggestionId = b
        });

        await player.PostAsJsonAsync("/api/votes", new
        {
            SuggestionId = a,
            Score = 6
        });

        var resp = await admin.PostAsJsonAsync("/api/admin/unlink-suggestions", new { suggestionId = a });
        resp.EnsureSuccessStatusCode();

        await factory.WithDbContextAsync(db =>
        {
            try
            {
                Assert.Empty(db.Votes);

                Assert.All(db.Suggestions, s => Assert.Null(s.ParentSuggestionId));
                return Task.CompletedTask;
            }
            catch (Exception exception)
            {
                return Task.FromException(exception);
            }
        });
    }

    [Fact]
    public async Task Reset_and_factory_reset_clear_state()
    {
        await using var factory = new TestWebApplicationFactory();
        var admin = factory.CreateClientWithCookies();
        await admin.RegisterAsync("admin", admin: true);
        var player = factory.CreateClientWithCookies();
        await player.RegisterAsync("player");
        await player.CreateSuggestionAsync("Keep");

        var reset = await admin.PostAsJsonAsync("/api/admin/reset", new { });
        reset.EnsureSuccessStatusCode();

        await factory.WithDbContextAsync(db =>
        {
            try
            {
                Assert.Empty(db.Suggestions);

                Assert.Empty(db.Votes);

                Assert.All(db.Players, p => Assert.Equal(Phase.Suggest, p.CurrentPhase));
                return Task.CompletedTask;
            }
            catch (Exception exception)
            {
                return Task.FromException(exception);
            }
        });

        var factoryReset = await admin.PostAsJsonAsync("/api/admin/factory-reset", new { });
        factoryReset.EnsureSuccessStatusCode();

        await factory.WithDbContextAsync(db =>
        {
            try
            {
                Assert.Empty(db.Players);

                Assert.Single(db.AppState);
                return Task.CompletedTask;
            }
            catch (Exception exception)
            {
                return Task.FromException(exception);
            }
        });
    }

    [Fact]
    public async Task Admin_results_closing_moves_back_to_vote_and_clears_finalize()
    {
        await using var factory = new TestWebApplicationFactory();
        var admin = factory.CreateClientWithCookies();
        await admin.RegisterAsync("admin", admin: true);
        var player = factory.CreateClientWithCookies();
        await player.RegisterAsync("player");

        var open = await admin.PostAsJsonAsync("/api/admin/results", new { resultsOpen = true });
        open.EnsureSuccessStatusCode();

        await factory.WithDbContextAsync(async db =>
        {
            var p = await db.Players.FirstAsync(x => !x.IsAdmin);
            p.VotesFinal = true;
            await db.SaveChangesAsync();
        });

        var beforeState = await factory.WithDbContextAsync(async db => await db.AppState.AsNoTracking().FirstAsync());
        await Task.Delay(5);
        var close = await admin.PostAsJsonAsync("/api/admin/results", new { resultsOpen = false });
        close.EnsureSuccessStatusCode();

        await factory.WithDbContextAsync(async db =>
        {
            var p = await db.Players.FirstAsync(x => !x.IsAdmin);
            Assert.Equal(Phase.Vote, p.CurrentPhase);
            Assert.False(p.VotesFinal);
            var state = await db.AppState.AsNoTracking().FirstAsync();
            Assert.False(state.ResultsOpen);
            Assert.True(state.UpdatedAt > beforeState.UpdatedAt);
        });
    }

    [Fact]
    public async Task Vote_status_lists_waiting_players()
    {
        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 p1 = factory.CreateClientWithCookies();
        await p1.RegisterAsync("alice");
        var p2 = factory.CreateClientWithCookies();
        await p2.RegisterAsync("bob");
        var s = await p1.CreateSuggestionAsync("Game");
        await p1.PostAsJsonAsync("/api/me/phase/next", new { });
        await p2.PostAsJsonAsync("/api/me/phase/next", new { });
        await p1.PostAsJsonAsync("/api/votes", new
        {
            SuggestionId = s,
            Score = 5
        });
        await p1.PostAsJsonAsync("/api/votes/finalize", new { Final = true });

        var status = await admin.GetFromJsonAsync<JsonElement>("/api/admin/vote-status");
        Assert.False(status.GetProperty("ready").GetBoolean());
        var waiting = status.GetProperty("waiting").EnumerateArray().Select(e => e.GetString()).ToList();
        Assert.Contains("bob-name", waiting);
    }

    [Fact]
    public async Task Grant_joker_in_vote_sets_flag_and_unfinalizes()
    {
        await using var factory = new TestWebApplicationFactory();
        var admin = factory.CreateClientWithCookies();
        await admin.RegisterAsync("admin", admin: true);

        var p = factory.CreateClientWithCookies();
        await p.RegisterAsync("player");
        await p.PostAsJsonAsync("/api/me/phase/next", new { });
        await p.PostAsJsonAsync("/api/votes/finalize", new { Final = true });

        var give = await admin.PostAsJsonAsync("/api/admin/joker", new { playerId = (await p.GetProfileIdAsync()) });
        give.EnsureSuccessStatusCode();

        await factory.WithDbContextAsync(async db =>
        {
            var player = await db.Players.SingleAsync(x => x.Username == "player");
            Assert.True(player.HasJoker);
            Assert.False(player.VotesFinal);
        });
    }

    [Fact]
    public async Task Link_requires_vote_phase_and_reparents_votes_reset()
    {
        await using var factory = new TestWebApplicationFactory();
        var admin = factory.CreateClientWithCookies();
        await admin.RegisterAsync("admin", admin: true);
        var player = factory.CreateClientWithCookies();
        await player.RegisterAsync("linker");

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

        var beforeVotePhase = await admin.PostAsJsonAsync("/api/admin/link-suggestions", new
        {
            SourceSuggestionId = a,
            TargetSuggestionId = b
        });
        Assert.Equal(HttpStatusCode.BadRequest, beforeVotePhase.StatusCode);

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

        await player.PostAsJsonAsync("/api/votes", new
        {
            SuggestionId = a,
            Score = 3
        });
        await player.PostAsJsonAsync("/api/votes/finalize", new { Final = true });

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

        await factory.WithDbContextAsync(async db =>
        {
            var votes = await db.Votes.ToListAsync();
            Assert.Empty(votes);
            var p = await db.Players.SingleAsync(x => x.Username == "linker");
            Assert.False(p.VotesFinal);
        });
    }

    [Fact]
    public async Task Link_unfinalizes_all_players()
    {
        await using var factory = new TestWebApplicationFactory();
        var admin = factory.CreateClientWithCookies();
        await admin.RegisterAsync("admin", admin: true);
        var p1 = factory.CreateClientWithCookies();
        await p1.RegisterAsync("p1");
        var p2 = factory.CreateClientWithCookies();
        await p2.RegisterAsync("p2");

        var a = await p1.CreateSuggestionAsync("A");
        var b = await p1.CreateSuggestionAsync("B");

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

        await p1.PostAsJsonAsync("/api/votes/finalize", new { Final = true });
        await p2.PostAsJsonAsync("/api/votes/finalize", new { Final = true });

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

        await factory.WithDbContextAsync(async db =>
        {
            var players = await db.Players.Where(p => !p.IsAdmin).ToListAsync();
            Assert.All(players, p => Assert.False(p.VotesFinal));
        });
    }

    [Fact]
    public async Task Unlink_not_found_returns_empty_payload()
    {
        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 resp = await admin.PostAsJsonAsync("/api/admin/unlink-suggestions", new { suggestionId = 9999 });
        resp.EnsureSuccessStatusCode();
        var json = await resp.Content.ReadFromJsonAsync<JsonElement>();
        Assert.Equal(0, json.GetProperty("unlinkedSuggestionIds").GetArrayLength());
    }

    [Fact]
    public async Task Reset_clears_flags_and_factory_reset_seeds_defaults()
    {
        await using var factory = new TestWebApplicationFactory();
        var admin = factory.CreateClientWithCookies();
        await admin.RegisterAsync("admin", admin: true);
        var p = factory.CreateClientWithCookies();
        await p.RegisterAsync("flags");

        await factory.WithDbContextAsync(async db =>
        {
            var player = await db.Players.SingleAsync(x => x.Username == "flags");
            player.HasJoker = true;
            player.VotesFinal = true;
            await db.SaveChangesAsync();
        });

        var reset = await admin.PostAsJsonAsync("/api/admin/reset", new { });
        reset.EnsureSuccessStatusCode();

        await factory.WithDbContextAsync(async db =>
        {
            var player = await db.Players.SingleAsync(x => x.Username == "flags");
            Assert.False(player.HasJoker);
            Assert.False(player.VotesFinal);
            var state = await db.AppState.AsNoTracking().FirstAsync();
            Assert.False(state.ResultsOpen);
        });

        var factoryReset = await admin.PostAsJsonAsync("/api/admin/factory-reset", new { });
        factoryReset.EnsureSuccessStatusCode();
        await factory.WithDbContextAsync(async db =>
        {
            var state = await db.AppState.AsNoTracking().FirstAsync();
            Assert.False(state.ResultsOpen);
        });
    }
}
