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

namespace GameList.Tests;

public class AuthTests
{
    [Fact]
    public async Task Register_trims_limits_and_sets_cookie_and_normalized_username()
    {
        await using var factory = new TestWebApplicationFactory();
        var client = factory.CreateClientWithCookies();

        var response = await client.PostAsJsonAsync("/api/auth/register", new
        {
            Username = "  MixedCaseUser  ",
            Password = "Pass123!",
            DisplayName = "  Display Name  ",
            AdminKey = (string?)null
        });

        response.EnsureSuccessStatusCode();
        Assert.True(response.Headers.TryGetValues("Set-Cookie", out var cookies) && cookies.Any(c => c.Contains(PlayerIdentityExtensions.PlayerCookieName)));

        await factory.WithDbContextAsync(async db =>
        {
            var player = await db.Players.AsNoTracking().SingleAsync(p => p.Username == "MixedCaseUser");
            Assert.Equal("mixedcaseuser", player.NormalizedUsername);
            Assert.True(player.DisplayName!.Length <= 16);
            Assert.NotEqual(Array.Empty<byte>(), player.PasswordHash);
            Assert.NotEqual(Array.Empty<byte>(), player.PasswordSalt);
        });
    }

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

        var tooLongUser = new string('u', 25);
        var userResp = await client.PostAsJsonAsync("/api/auth/register", new
        {
            Username = tooLongUser,
            Password = "Pass123!",
            DisplayName = "short"
        });
        Assert.Equal(HttpStatusCode.BadRequest, userResp.StatusCode);

        var longDisplay = new string('d', 17);
        var displayResp = await client.PostAsJsonAsync("/api/auth/register", new
        {
            Username = "okuser",
            Password = "Pass123!",
            DisplayName = longDisplay
        });

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

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

        await factory.WithDbContextAsync(async db =>
        {
            var player = await db.Players.FirstAsync();
            player.DisplayName = null;
            player.LastLoginAt = DateTimeOffset.UnixEpoch;
            await db.SaveChangesAsync();
        });

        var login = await client.LoginAsync("loginfill", "Pass123!");
        login.EnsureSuccessStatusCode();

        await factory.WithDbContextAsync(async db =>
        {
            var player = await db.Players.AsNoTracking().SingleAsync();
            Assert.NotEqual(DateTimeOffset.UnixEpoch, player.LastLoginAt);
            Assert.Equal("loginfill", player.DisplayName);
        });
    }

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

        var response = await client.RegisterAsync("adminuser", admin: true);

        response.EnsureSuccessStatusCode();
        var json = await response.Content.ReadFromJsonAsync<JsonElement>();
        Assert.True(json.GetProperty("isAdmin").GetBoolean());
    }

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

        var first = await client.RegisterAsync("duplicate");
        first.EnsureSuccessStatusCode();

        var second = await client.RegisterAsync("duplicate");

        Assert.Equal(HttpStatusCode.Conflict, second.StatusCode);
    }

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

        await client.RegisterAsync("player1");

        var login = await client.LoginAsync("player1", "wrongpass");

        Assert.Equal(HttpStatusCode.Unauthorized, login.StatusCode);
    }

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

        var missing = await client.PostAsJsonAsync("/api/auth/register", new
        {
            Username = "",
            Password = "",
            DisplayName = ""
        });
        Assert.Equal(HttpStatusCode.BadRequest, missing.StatusCode);

        var badKey = await client.PostAsJsonAsync("/api/auth/register", new
        {
            Username = "u",
            Password = "p",
            DisplayName = "d",
            AdminKey = "wrong"
        });
        Assert.Equal(HttpStatusCode.BadRequest, badKey.StatusCode);
    }

    [Fact]
    public async Task Non_admin_cannot_access_admin_routes()
    {
        await using var factory = new TestWebApplicationFactory();
        var player = factory.CreateClientWithCookies();
        await player.RegisterAsync("regular");

        var resp = await player.GetAsync("/api/admin/vote-status");
        Assert.Equal(HttpStatusCode.Unauthorized, resp.StatusCode);
        var json = await resp.Content.ReadFromJsonAsync<JsonElement>();
        Assert.Equal("Unauthorized", json.GetProperty("title").GetString());
        Assert.Equal("Unauthorized", json.GetProperty("detail").GetString());
        Assert.Equal("Unauthorized", json.GetProperty("error").GetString());
    }

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

        var resp = await admin.GetAsync("/api/admin/vote-status");
        resp.EnsureSuccessStatusCode();
    }

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

        var resp = await client.PostAsync("/api/auth/logout", null);
        resp.EnsureSuccessStatusCode();
        Assert.True(resp.Headers.TryGetValues("Set-Cookie", out var cookies) && cookies.Any(c => c.Contains("player")));
    }
}
