using Microsoft.Extensions.Options;

namespace NeonSprawl.Server.Game.PositionState;

/// <summary>Maps HTTP routes for authoritative position read and move (NS-15 / NS-16 / NS-19).</summary>
public static class PositionStateApi
{
    public static WebApplication MapPositionStateApi(this WebApplication app)
    {
        app.MapGet(
            "/game/players/{id}/position",
            (string id, IPositionStateStore store) =>
            {
                if (!store.TryGetPosition(id, out var snap))
                {
                    return Results.NotFound();
                }

                var body = new PositionStateResponse
                {
                    SchemaVersion = PositionStateResponse.CurrentSchemaVersion,
                    PlayerId = id,
                    Position = new PositionVector { X = snap.X, Y = snap.Y, Z = snap.Z },
                    Sequence = snap.Sequence,
                };
                return Results.Json(body);
            });

        app.MapPost(
            "/game/players/{id}/move",
            (string id, MoveCommandRequest? body, IPositionStateStore store, IOptions<GamePositionOptions> gameOptions) =>
            {
                if (body is null || body.SchemaVersion != MoveCommandRequest.CurrentSchemaVersion)
                {
                    return Results.BadRequest();
                }

                var target = body.Target;
                if (target is null)
                {
                    return Results.BadRequest();
                }

                if (!store.TryGetPosition(id, out var current))
                {
                    return Results.NotFound();
                }

                var rules = gameOptions.Value.MovementValidation;
                if (!MoveCommandValidation.TryValidate(current, target, rules, out var reasonCode))
                {
                    var rejected = new MoveCommandRejectedResponse
                    {
                        SchemaVersion = MoveCommandRejectedResponse.CurrentSchemaVersion,
                        ReasonCode = reasonCode,
                    };
                    return Results.Json(rejected, statusCode: StatusCodes.Status400BadRequest);
                }

                if (!store.TryApplyMoveTarget(id, target.X, target.Y, target.Z, out var snap))
                {
                    return Results.NotFound();
                }

                var response = new PositionStateResponse
                {
                    SchemaVersion = PositionStateResponse.CurrentSchemaVersion,
                    PlayerId = id,
                    Position = new PositionVector { X = snap.X, Y = snap.Y, Z = snap.Z },
                    Sequence = snap.Sequence,
                };
                return Results.Json(response);
            });

        app.MapPost(
            "/game/players/{id}/move-stream",
            (string id, MoveStreamRequest? body, IPositionStateStore store, IOptions<GamePositionOptions> gameOptions) =>
            {
                if (body is null || body.SchemaVersion != MoveStreamRequest.CurrentSchemaVersion)
                {
                    return Results.BadRequest();
                }

                var targets = body.Targets;
                if (targets is null || targets.Count == 0 || targets.Count > MoveStreamRequest.MaxTargets)
                {
                    return Results.BadRequest();
                }

                if (!store.TryGetPosition(id, out var current))
                {
                    return Results.NotFound();
                }

                var rules = gameOptions.Value.MovementValidation;
                // Validate the full chain before applying so a mid-chain reject does not leave a partial apply.
                var probe = current;
                foreach (var target in targets)
                {
                    if (!MoveCommandValidation.TryValidate(probe, target, rules, out var reasonCode))
                    {
                        var rejected = new MoveCommandRejectedResponse
                        {
                            SchemaVersion = MoveCommandRejectedResponse.CurrentSchemaVersion,
                            ReasonCode = reasonCode,
                        };
                        return Results.Json(rejected, statusCode: StatusCodes.Status400BadRequest);
                    }

                    probe = new PositionSnapshot(target.X, target.Y, target.Z, probe.Sequence);
                }

                PositionSnapshot lastSnap = current;
                foreach (var target in targets)
                {
                    if (!store.TryApplyMoveTarget(id, target.X, target.Y, target.Z, out var snap))
                    {
                        return Results.NotFound();
                    }

                    lastSnap = snap;
                }

                var response = new PositionStateResponse
                {
                    SchemaVersion = PositionStateResponse.CurrentSchemaVersion,
                    PlayerId = id,
                    Position = new PositionVector { X = lastSnap.X, Y = lastSnap.Y, Z = lastSnap.Z },
                    Sequence = lastSnap.Sequence,
                };
                return Results.Json(response);
            });

        return app;
    }
}
