using Microsoft.Extensions.Options;

namespace NeonSprawl.Server.Game.PositionState;

/// <summary>PostgreSQL-backed <see cref="IPositionStateStore"/> (NS-17). Player ids are stored normalized: invariant lower + trim for case-insensitive parity with <see cref="InMemoryPositionStateStore"/>.</summary>
public sealed class PostgresPositionStateStore(
    Npgsql.NpgsqlDataSource dataSource,
    IOptions<GamePositionOptions> positionOptions) : IPositionStateStore
{
    private readonly GamePositionOptions gameOptions = RequireValidOptions(positionOptions.Value);

    private static GamePositionOptions RequireValidOptions(GamePositionOptions options)
    {
        if (options.DevPlayerId.Trim().Length == 0)
        {
            throw new InvalidOperationException("Game:DevPlayerId must be non-empty.");
        }

        return options;
    }

    public bool TryGetPosition(string playerId, out PositionSnapshot snapshot)
    {
        var norm = NormalizePlayerId(playerId);
        if (norm.Length == 0)
        {
            snapshot = default;
            return false;
        }

        PostgresPositionBootstrap.EnsureSchema(dataSource);

        using var conn = dataSource.OpenConnection();
        using var cmd = new Npgsql.NpgsqlCommand(
            "SELECT pos_x, pos_y, pos_z, sequence FROM player_position WHERE player_id = @pid;",
            conn);
        cmd.Parameters.AddWithValue("pid", norm);
        using var reader = cmd.ExecuteReader();
        if (!reader.Read())
        {
            snapshot = default;
            return false;
        }

        snapshot = ReadSnapshot(reader);
        return true;
    }

    public bool TryApplyMoveTarget(string playerId, double x, double y, double z, out PositionSnapshot snapshot)
    {
        var norm = NormalizePlayerId(playerId);
        if (norm.Length == 0)
        {
            snapshot = default;
            return false;
        }

        PostgresPositionBootstrap.EnsureSchema(dataSource);

        using var conn = dataSource.OpenConnection();
        using var cmd = new Npgsql.NpgsqlCommand(
            """
            UPDATE player_position
            SET pos_x = @x, pos_y = @y, pos_z = @z,
                sequence = sequence + 1,
                updated_at = now()
            WHERE player_id = @pid
            RETURNING pos_x, pos_y, pos_z, sequence;
            """,
            conn);
        cmd.Parameters.AddWithValue("pid", norm);
        cmd.Parameters.AddWithValue("x", x);
        cmd.Parameters.AddWithValue("y", y);
        cmd.Parameters.AddWithValue("z", z);
        using var reader = cmd.ExecuteReader();
        if (!reader.Read())
        {
            snapshot = default;
            return false;
        }

        snapshot = ReadSnapshot(reader);
        return true;
    }

    private static PositionSnapshot ReadSnapshot(Npgsql.NpgsqlDataReader reader)
    {
        var seq = reader.GetInt64(3);
        return new PositionSnapshot(
            reader.GetDouble(0),
            reader.GetDouble(1),
            reader.GetDouble(2),
            unchecked((ulong)seq));
    }

    private static string NormalizePlayerId(string? playerId)
    {
        var t = playerId?.Trim();
        if (string.IsNullOrEmpty(t))
        {
            return string.Empty;
        }

        return t.ToLowerInvariant();
    }
}
