namespace NeonSprawl.Server.Game.PositionState;

/// <summary>
/// Applies NS-17 DDL (once per process, idempotent) and seeds the configured dev player row.
/// Used at host startup and by integration tests after <c>TRUNCATE</c>.
/// </summary>
public static class PostgresPositionBootstrap
{
    private static readonly string DdlRelativePath = Path.Combine("db", "migrations", "V001__player_position.sql");

    private static readonly object SchemaGate = new();
    private static int _schemaReady;

    /// <summary>
    /// Ensures <c>player_position</c> exists. Heavy work (read migration, run DDL) runs at most once per process.
    /// </summary>
    public static void EnsureSchema(Npgsql.NpgsqlDataSource dataSource)
    {
        if (System.Threading.Volatile.Read(ref _schemaReady) != 0)
        {
            return;
        }

        lock (SchemaGate)
        {
            if (System.Threading.Volatile.Read(ref _schemaReady) != 0)
            {
                return;
            }

            var ddlPath = Path.Combine(AppContext.BaseDirectory, DdlRelativePath);
            if (!File.Exists(ddlPath))
            {
                throw new FileNotFoundException($"NS-17 DDL not found at '{ddlPath}'.", ddlPath);
            }

            var ddl = File.ReadAllText(ddlPath);
            using var conn = dataSource.OpenConnection();
            using (var cmd = new Npgsql.NpgsqlCommand(ddl, conn))
            {
                cmd.ExecuteNonQuery();
            }

            System.Threading.Volatile.Write(ref _schemaReady, 1);
        }
    }

    /// <summary>Idempotent insert for configured seed players at default position.</summary>
    public static void SeedDevPlayer(Npgsql.NpgsqlDataSource dataSource, GamePositionOptions options)
    {
        using var conn = dataSource.OpenConnection();
        SeedDevPlayer(conn, options);
    }

    /// <summary>Idempotent insert using an existing open connection (e.g. test harness after <c>TRUNCATE</c>).</summary>
    public static void SeedDevPlayer(Npgsql.NpgsqlConnection connection, GamePositionOptions options)
    {
        var devNorm = NormalizePlayerId(options.DevPlayerId);
        var p = options.DefaultPosition ?? new DefaultPositionOptions();
        using var cmd = new Npgsql.NpgsqlCommand(
            """
            INSERT INTO player_position (player_id, pos_x, pos_y, pos_z, sequence)
            VALUES (@pid, @x, @y, @z, 0)
            ON CONFLICT (player_id) DO NOTHING;
            """,
            connection);
        cmd.Parameters.AddWithValue("pid", devNorm);
        cmd.Parameters.AddWithValue("x", p.X);
        cmd.Parameters.AddWithValue("y", p.Y);
        cmd.Parameters.AddWithValue("z", p.Z);
        cmd.ExecuteNonQuery();
    }

    private static string NormalizePlayerId(string? playerId)
    {
        var t = playerId?.Trim();
        if (string.IsNullOrEmpty(t))
        {
            throw new InvalidOperationException("Game:DevPlayerId must be non-empty.");
        }

        return t.ToLowerInvariant();
    }
}
