using System.Net;
using System.Net.Http.Json;
using System.Text;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
using NeonSprawl.Server.Game.PositionState;
using Xunit;

namespace NeonSprawl.Server.Tests.Game.PositionState;

/// <summary>
/// NS-16 / NS-19: integration tests for <c>POST /game/players/{{id}}/move</c> on <see cref="NeonSprawl.Server.Game.PositionState.PositionStateApi"/>.
/// Isolated host per test (fresh in-memory store).
/// </summary>
public class MoveCommandApiTests
{
    [Fact]
    public async Task PostMove_ShouldPersistTargetAndIncrementSequence_WhenDevPlayerPostsValidMove()
    {
        // Arrange
        await using var factory = new InMemoryWebApplicationFactory();
        var client = factory.CreateClient();
        var cmd = new MoveCommandRequest
        {
            SchemaVersion = MoveCommandRequest.CurrentSchemaVersion,
            Target = new PositionVector { X = 1.5, Y = 0.0, Z = 2.25 },
        };
        var beforeMove = await client.GetFromJsonAsync<PositionStateResponse>("/game/players/dev-local-1/position");

        // Act
        var postResponse = await client.PostAsJsonAsync("/game/players/dev-local-1/move", cmd);

        // Assert
        Assert.NotNull(beforeMove);
        var seq0 = beforeMove!.Sequence;

        Assert.Equal(HttpStatusCode.OK, postResponse.StatusCode);
        var postBody = await postResponse.Content.ReadFromJsonAsync<PositionStateResponse>();
        var afterMove = await client.GetFromJsonAsync<PositionStateResponse>("/game/players/dev-local-1/position");
        Assert.NotNull(postBody);
        Assert.Equal(PositionStateResponse.CurrentSchemaVersion, postBody!.SchemaVersion);
        Assert.Equal("dev-local-1", postBody.PlayerId);
        Assert.Equal(seq0 + 1, postBody.Sequence);
        Assert.Equal(1.5, postBody.Position.X);
        Assert.Equal(0.0, postBody.Position.Y);
        Assert.Equal(2.25, postBody.Position.Z);

        Assert.NotNull(afterMove);
        Assert.Equal(postBody.Position.X, afterMove!.Position.X);
        Assert.Equal(postBody.Position.Y, afterMove.Position.Y);
        Assert.Equal(postBody.Position.Z, afterMove.Position.Z);
        Assert.Equal(postBody.Sequence, afterMove.Sequence);
    }

    [Fact]
    public async Task PostMove_ShouldReturnNotFound_WhenPlayerIsUnknown()
    {
        // Arrange
        await using var factory = new InMemoryWebApplicationFactory();
        var client = factory.CreateClient();
        var cmd = new MoveCommandRequest
        {
            SchemaVersion = MoveCommandRequest.CurrentSchemaVersion,
            Target = new PositionVector { X = 0, Y = 0, Z = 0 },
        };

        // Act
        var response = await client.PostAsJsonAsync("/game/players/unknown-player/move", cmd);

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

    [Fact]
    public async Task PostMove_ShouldReturnBadRequest_WhenSchemaVersionIsWrong()
    {
        // Arrange
        await using var factory = new InMemoryWebApplicationFactory();
        var client = factory.CreateClient();
        var cmd = new MoveCommandRequest
        {
            SchemaVersion = 999,
            Target = new PositionVector { X = 0, Y = 0, Z = 0 },
        };

        // Act
        var response = await client.PostAsJsonAsync("/game/players/dev-local-1/move", cmd);

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

    [Fact]
    public async Task PostMove_ShouldReturnBadRequest_WhenTargetIsMissing()
    {
        // Arrange
        await using var factory = new InMemoryWebApplicationFactory();
        var client = factory.CreateClient();
        var content = new StringContent(
            "{\"schemaVersion\":1}",
            Encoding.UTF8,
            "application/json");

        // Act
        var response = await client.PostAsync("/game/players/dev-local-1/move", content);

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

    [Fact]
    public async Task PostMove_ShouldReturn400WithReason_WhenHorizontalStepTooLarge()
    {
        // Arrange

        await using var factory = new InMemoryWebApplicationFactory().WithWebHostBuilder(b =>
        {
            b.ConfigureTestServices(services =>
            {
                services.PostConfigure<GamePositionOptions>(o =>
                {
                    o.MovementValidation.HorizontalStepEnabled = true;
                    o.MovementValidation.MaxHorizontalStep = 1.0;
                });
            });
        });
        var client = factory.CreateClient();
        var cmd = new MoveCommandRequest
        {
            SchemaVersion = MoveCommandRequest.CurrentSchemaVersion,
            Target = new PositionVector { X = 0, Y = 0.9, Z = 0 },
        };
        // Act
        var response = await client.PostAsJsonAsync("/game/players/dev-local-1/move", cmd);

        // Assert
        Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
        var body = await response.Content.ReadFromJsonAsync<MoveCommandRejectedResponse>();
        Assert.NotNull(body);
        Assert.Equal(MoveCommandRejectedResponse.CurrentSchemaVersion, body!.SchemaVersion);
        Assert.Equal(MoveCommandReasonCodes.HorizontalStepExceeded, body.ReasonCode);
    }

    [Fact]
    public async Task PostMove_ShouldReturn400WithReason_WhenVerticalStepTooLarge()
    {
        // Arrange

        await using var factory = new InMemoryWebApplicationFactory().WithWebHostBuilder(b =>
        {
            b.ConfigureTestServices(services =>
            {
                services.PostConfigure<GamePositionOptions>(o => o.MovementValidation.MaxVerticalStep = 0.5);
            });
        });
        var client = factory.CreateClient();
        var cmd = new MoveCommandRequest
        {
            SchemaVersion = MoveCommandRequest.CurrentSchemaVersion,
            Target = new PositionVector { X = -5, Y = 2.0, Z = -5 },
        };
        // Act
        var response = await client.PostAsJsonAsync("/game/players/dev-local-1/move", cmd);

        // Assert
        Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
        var body = await response.Content.ReadFromJsonAsync<MoveCommandRejectedResponse>();
        Assert.NotNull(body);
        Assert.Equal(MoveCommandReasonCodes.VerticalStepExceeded, body!.ReasonCode);
    }

    [Fact]
    public async Task PostMove_ShouldReturn400WithoutReasonBody_WhenSchemaInvalid()
    {
        // Arrange

        await using var factory = new InMemoryWebApplicationFactory();
        var client = factory.CreateClient();
        // Act
        var response = await client.PostAsync(
            "/game/players/dev-local-1/move",
            new StringContent("{\"schemaVersion\":999,\"target\":{\"x\":0,\"y\":0,\"z\":0}}", Encoding.UTF8, "application/json"));
        // Assert

        Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
        var text = await response.Content.ReadAsStringAsync();
        Assert.DoesNotContain("reasonCode", text, StringComparison.OrdinalIgnoreCase);
    }
}
