using System.Net;
using System.Net.Http.Json;
using System.Text;
using NeonSprawl.Server.Game.Interaction;
using NeonSprawl.Server.Game.PositionState;
using Xunit;

namespace NeonSprawl.Server.Tests.Game.Interaction;

/// <summary>Integration tests for <see cref="InteractionApi"/> (NS-18). NEO-41: <see cref="InteractionResourceNodeGatherXpTests"/> covers gather XP on <c>resource_node</c> kind.</summary>
public class InteractionApiTests
{
    [Fact]
    public async Task PostInteract_ShouldAllow_WhenExactlyAtHorizontalRadius()
    {
        // Arrange
        await using var factory = new InMemoryWebApplicationFactory();
        var client = factory.CreateClient();
        var move = new MoveCommandRequest
        {
            SchemaVersion = MoveCommandRequest.CurrentSchemaVersion,
            Target = new PositionVector { X = 3, Y = 0, Z = 0 },
        };
        var moveResponse = await client.PostAsJsonAsync("/game/players/dev-local-1/move", move);

        // Act
        var response = await client.PostAsJsonAsync(
            "/game/players/dev-local-1/interact",
            new InteractionRequest
            {
                SchemaVersion = InteractionRequest.CurrentSchemaVersion,
                InteractableId = PrototypeInteractableRegistry.PrototypeTerminalId,
            });

        // Assert
        var body = await response.Content.ReadFromJsonAsync<InteractionResponse>();
        Assert.Equal(HttpStatusCode.OK, moveResponse.StatusCode);
        Assert.Equal(HttpStatusCode.OK, response.StatusCode);
        Assert.NotNull(body);
        Assert.True(body!.Allowed);
    }

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

        await using var factory = new InMemoryWebApplicationFactory();
        var client = factory.CreateClient();
        var move = new MoveCommandRequest
        {
            SchemaVersion = MoveCommandRequest.CurrentSchemaVersion,
            Target = new PositionVector { X = 0, Y = 0.9, Z = 0 },
        };
        var moveResp = await client.PostAsJsonAsync("/game/players/dev-local-1/move", move);

        var req = new InteractionRequest
        {
            SchemaVersion = InteractionRequest.CurrentSchemaVersion,
            InteractableId = PrototypeInteractableRegistry.PrototypeTerminalId,
        };

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

        // Assert
        Assert.Equal(HttpStatusCode.OK, moveResp.StatusCode);
        Assert.Equal(HttpStatusCode.OK, response.StatusCode);
        var body = await response.Content.ReadFromJsonAsync<InteractionResponse>();
        Assert.NotNull(body);
        Assert.True(body!.Allowed);
        Assert.Null(body.ReasonCode);
        Assert.Equal(PrototypeInteractableRegistry.PrototypeTerminalId, body.InteractableId);
        Assert.NotNull(body.Payload);
        Assert.Equal("placeholder", body.Payload!.Kind);
    }

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

        await using var factory = new InMemoryWebApplicationFactory();
        var client = factory.CreateClient();
        var req = new InteractionRequest
        {
            SchemaVersion = InteractionRequest.CurrentSchemaVersion,
            InteractableId = PrototypeInteractableRegistry.PrototypeTerminalId,
        };
        // Act

        var response = await client.PostAsJsonAsync("/game/players/dev-local-1/interact", req);
        // Assert

        Assert.Equal(HttpStatusCode.OK, response.StatusCode);
        var body = await response.Content.ReadFromJsonAsync<InteractionResponse>();
        Assert.NotNull(body);
        Assert.False(body!.Allowed);
        Assert.Equal(InteractionApi.ReasonOutOfRange, body.ReasonCode);
    }

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

        await using var factory = new InMemoryWebApplicationFactory();
        var client = factory.CreateClient();
        var req = new InteractionRequest
        {
            SchemaVersion = InteractionRequest.CurrentSchemaVersion,
            InteractableId = "no_such_thing",
        };
        // Act

        var response = await client.PostAsJsonAsync("/game/players/dev-local-1/interact", req);
        // Assert

        Assert.Equal(HttpStatusCode.OK, response.StatusCode);
        var body = await response.Content.ReadFromJsonAsync<InteractionResponse>();
        Assert.NotNull(body);
        Assert.False(body!.Allowed);
        Assert.Equal(InteractionApi.ReasonUnknownInteractable, body.ReasonCode);
    }

    [Fact]
    public async Task PostInteract_ShouldBeCaseInsensitive_ForInteractableId()
    {
        // Arrange
        await using var factory = new InMemoryWebApplicationFactory();
        var client = factory.CreateClient();
        var move = new MoveCommandRequest
        {
            SchemaVersion = MoveCommandRequest.CurrentSchemaVersion,
            Target = new PositionVector { X = 0, Y = 0, Z = 0 },
        };
        var moveResponse = await client.PostAsJsonAsync("/game/players/dev-local-1/move", move);

        var req = new InteractionRequest
        {
            SchemaVersion = InteractionRequest.CurrentSchemaVersion,
            InteractableId = "  PROTOTYPE_TERMINAL  ",
        };

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

        // Assert
        var body = await response.Content.ReadFromJsonAsync<InteractionResponse>();
        Assert.Equal(HttpStatusCode.OK, moveResponse.StatusCode);
        Assert.Equal(HttpStatusCode.OK, response.StatusCode);
        Assert.NotNull(body);
        Assert.True(body!.Allowed);
    }

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

        await using var factory = new InMemoryWebApplicationFactory();
        var client = factory.CreateClient();
        var req = new InteractionRequest
        {
            SchemaVersion = 999,
            InteractableId = PrototypeInteractableRegistry.PrototypeTerminalId,
        };
        // Act

        var response = await client.PostAsJsonAsync("/game/players/dev-local-1/interact", req);
        // Assert

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

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

        await using var factory = new InMemoryWebApplicationFactory();
        var client = factory.CreateClient();
        var content = new StringContent(
            $"{{\"schemaVersion\":{InteractionRequest.CurrentSchemaVersion}}}",
            Encoding.UTF8,
            "application/json");
        // Act

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

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

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

        await using var factory = new InMemoryWebApplicationFactory();
        var client = factory.CreateClient();
        var req = new InteractionRequest
        {
            SchemaVersion = InteractionRequest.CurrentSchemaVersion,
            InteractableId = "   \t  ",
        };
        // Act

        var response = await client.PostAsJsonAsync("/game/players/dev-local-1/interact", req);
        // Assert

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

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

        await using var factory = new InMemoryWebApplicationFactory();
        var client = factory.CreateClient();
        var req = new InteractionRequest
        {
            SchemaVersion = InteractionRequest.CurrentSchemaVersion,
            InteractableId = PrototypeInteractableRegistry.PrototypeTerminalId,
        };
        // Act

        var response = await client.PostAsJsonAsync("/game/players/nobody-here/interact", req);
        // Assert

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

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

        // Act
        var far = await client.PostAsJsonAsync(
            "/game/players/dev-local-1/interact",
            new InteractionRequest
            {
                SchemaVersion = InteractionRequest.CurrentSchemaVersion,
                InteractableId = PrototypeInteractableRegistry.PrototypeTerminalId,
            });

        // Assert
        Assert.Equal(HttpStatusCode.OK, far.StatusCode);
        var farBody = await far.Content.ReadFromJsonAsync<InteractionResponse>();
        Assert.False(farBody!.Allowed);
        Assert.Equal(InteractionApi.ReasonOutOfRange, farBody.ReasonCode);

        // Act
        var move = new MoveCommandRequest
        {
            SchemaVersion = MoveCommandRequest.CurrentSchemaVersion,
            Target = new PositionVector { X = 0, Y = 0, Z = 0 },
        };
        var moveResponse = await client.PostAsJsonAsync("/game/players/dev-local-1/move", move);

        // Assert
        Assert.Equal(HttpStatusCode.OK, moveResponse.StatusCode);

        // Act
        var near = await client.PostAsJsonAsync(
            "/game/players/dev-local-1/interact",
            new InteractionRequest
            {
                SchemaVersion = InteractionRequest.CurrentSchemaVersion,
                InteractableId = PrototypeInteractableRegistry.PrototypeTerminalId,
            });

        // Assert
        Assert.Equal(HttpStatusCode.OK, near.StatusCode);
        var nearBody = await near.Content.ReadFromJsonAsync<InteractionResponse>();
        Assert.True(nearBody!.Allowed);
    }

    [Fact]
    public async Task PostInteract_ShouldAllowResourceNode_WhenPlayerInRangeOfResourceAnchor()
    {
        // Arrange
        await using var factory = new InMemoryWebApplicationFactory();
        var client = factory.CreateClient();
        var move = new MoveCommandRequest
        {
            SchemaVersion = MoveCommandRequest.CurrentSchemaVersion,
            Target = new PositionVector { X = 12, Y = 0.9, Z = -6 },
        };
        var moveResponse = await client.PostAsJsonAsync("/game/players/dev-local-1/move", move);

        // Act
        var response = await client.PostAsJsonAsync(
            "/game/players/dev-local-1/interact",
            new InteractionRequest
            {
                SchemaVersion = InteractionRequest.CurrentSchemaVersion,
                InteractableId = PrototypeInteractableRegistry.PrototypeResourceNodeAlphaId,
            });

        // Assert
        var body = await response.Content.ReadFromJsonAsync<InteractionResponse>();
        Assert.Equal(HttpStatusCode.OK, moveResponse.StatusCode);
        Assert.Equal(HttpStatusCode.OK, response.StatusCode);
        Assert.NotNull(body);
        Assert.True(body!.Allowed);
        Assert.Equal(PrototypeInteractableRegistry.PrototypeResourceNodeAlphaId, body.InteractableId);
    }

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

        await using var factory = new InMemoryWebApplicationFactory();
        var client = factory.CreateClient();
        // Act
        var response = await client.PostAsJsonAsync(
            "/game/players/dev-local-1/interact",
            new InteractionRequest
            {
                SchemaVersion = InteractionRequest.CurrentSchemaVersion,
                InteractableId = PrototypeInteractableRegistry.PrototypeResourceNodeAlphaId,
            });
        // Assert

        Assert.Equal(HttpStatusCode.OK, response.StatusCode);
        var body = await response.Content.ReadFromJsonAsync<InteractionResponse>();
        Assert.NotNull(body);
        Assert.False(body!.Allowed);
        Assert.Equal(InteractionApi.ReasonOutOfRange, body.ReasonCode);
    }
}
