using System.Net;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
using NeonSprawl.Server.Game.Mastery;
using NeonSprawl.Server.Game.Skills;
using Xunit;

namespace NeonSprawl.Server.Tests.Game.Mastery;

public sealed class MasteryFixtureApiTests
{
    private const string DevPlayer = "dev-local-1";
    private const string FixturePath = $"/game/players/{DevPlayer}/__dev/mastery-fixture";

    private static MasteryFixtureRequest ValidFixture(
        bool resetPerkState = true,
        Dictionary<string, int>? skillXp = null) =>
        new MasteryFixtureRequest
        {
            SchemaVersion = MasteryFixtureRequest.CurrentSchemaVersion,
            ResetPerkState = resetPerkState,
            SkillXp = skillXp ?? new Dictionary<string, int> { ["salvage"] = 0 },
        };

    private static async Task ArrangeSalvageProgressedWithTier1PickAsync(HttpClient client)
    {
        var grant = await client.PostAsJsonAsync(
            $"/game/players/{DevPlayer}/skill-progression",
            new SkillProgressionGrantRequest
            {
                SchemaVersion = SkillProgressionGrantRequest.CurrentSchemaVersion,
                SkillId = "salvage",
                Amount = 100,
                SourceKind = "activity",
            });
        Assert.Equal(HttpStatusCode.OK, grant.StatusCode);
        var pick = await client.PostAsJsonAsync(
            $"/game/players/{DevPlayer}/perk-state",
            new PerkBranchSelectRequest
            {
                SchemaVersion = PerkBranchSelectRequest.CurrentSchemaVersion,
                SkillId = "salvage",
                TierIndex = 1,
                BranchId = "scrap_efficiency",
            });
        Assert.Equal(HttpStatusCode.OK, pick.StatusCode);
    }

    [Fact]
    public async Task PostMasteryFixture_ShouldReturnNotFound_WhenRouteNotRegistered()
    {
        // Arrange
        await using var factory = new InMemoryWebApplicationFactory();
        var client = factory.WithWebHostBuilder(b =>
        {
            b.UseEnvironment("Production");
            b.UseSetting("Game:EnableMasteryFixtureApi", "false");
        }).CreateClient();

        // Act
        var response = await client.PostAsJsonAsync(FixturePath, ValidFixture());

        // Assert
        Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
    }

    [Fact]
    public async Task PostMasteryFixture_ShouldReturnBadRequest_WhenBodyNull()
    {
        // Arrange
        await using var factory = new InMemoryWebApplicationFactory();
        var client = factory.CreateClient();

        // Act
        var response = await client.PostAsync(
            FixturePath,
            new StringContent(string.Empty, Encoding.UTF8, "application/json"));

        // Assert
        Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
    }

    [Fact]
    public async Task PostMasteryFixture_ShouldReturnBadRequest_WhenSchemaVersionMismatch()
    {
        // Arrange
        await using var factory = new InMemoryWebApplicationFactory();
        var client = factory.CreateClient();
        var request = ValidFixture();
        request = new MasteryFixtureRequest
        {
            SchemaVersion = MasteryFixtureRequest.CurrentSchemaVersion + 99,
            ResetPerkState = request.ResetPerkState,
            SkillXp = request.SkillXp,
        };

        // Act
        var response = await client.PostAsJsonAsync(FixturePath, request);

        // Assert
        Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
    }

    [Fact]
    public async Task PostMasteryFixture_ShouldReturnBadRequest_WhenSkillXpNegative()
    {
        // Arrange
        await using var factory = new InMemoryWebApplicationFactory();
        var client = factory.CreateClient();

        // Act
        var response = await client.PostAsJsonAsync(
            FixturePath,
            ValidFixture(skillXp: new Dictionary<string, int> { ["salvage"] = -1 }));

        // Assert
        Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
    }

    [Fact]
    public async Task PostMasteryFixture_ShouldReturnNotFound_WhenPlayerUnknown()
    {
        // Arrange
        await using var factory = new InMemoryWebApplicationFactory();
        var client = factory.CreateClient();

        // Act
        var response = await client.PostAsJsonAsync(
            "/game/players/missing-player/__dev/mastery-fixture",
            ValidFixture());

        // Assert
        Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
    }

    [Fact]
    public async Task PostMasteryFixture_ShouldResetSalvageXpAndPerkState_ThenLevelTooLowDenies()
    {
        // Arrange
        await using var factory = new InMemoryWebApplicationFactory();
        var client = factory.CreateClient();
        await ArrangeSalvageProgressedWithTier1PickAsync(client);

        // Act
        var reset = await client.PostAsJsonAsync(FixturePath, ValidFixture());
        var deny = await client.PostAsJsonAsync(
            $"/game/players/{DevPlayer}/perk-state",
            new PerkBranchSelectRequest
            {
                SchemaVersion = PerkBranchSelectRequest.CurrentSchemaVersion,
                SkillId = "salvage",
                TierIndex = 1,
                BranchId = "scrap_efficiency",
            });

        // Assert
        Assert.Equal(HttpStatusCode.OK, reset.StatusCode);
        var resetBody = await reset.Content.ReadFromJsonAsync<MasteryFixtureResponse>();
        Assert.NotNull(resetBody);
        Assert.True(resetBody!.Applied);
        var progression = await client.GetFromJsonAsync<SkillProgressionSnapshotResponse>(
            $"/game/players/{DevPlayer}/skill-progression");
        var salvage = Assert.Single(progression!.Skills!, static s => s.Id == "salvage");
        Assert.Equal(0, salvage.Xp);
        Assert.Equal(1, salvage.Level);
        var perkGet = await client.GetFromJsonAsync<PerkStateSnapshotResponse>(
            $"/game/players/{DevPlayer}/perk-state");
        Assert.Empty(perkGet!.BranchPicks);
        Assert.Equal(HttpStatusCode.OK, deny.StatusCode);
        var envelope = await deny.Content.ReadFromJsonAsync<PerkBranchSelectResponse>();
        Assert.NotNull(envelope);
        Assert.False(envelope!.Selected);
        Assert.Equal(PerkUnlockReasonCodes.LevelTooLow, envelope.ReasonCode);
    }
}
