using System; using System.Collections.Generic; using System.Text.Json; using System.Threading.Tasks; using Supabase; using Postgrest; using Game.Data; using UnityEngine; // for PlayerPrefs using System.Text.RegularExpressions; public static class GameDb { private static Supabase.Client _client; private const string PrefsWalletKey = "WALLET_ADDRESS"; private const string PrefsName = "DISPLAY_NAME"; public static async Task InitAsync() { if (_client != null) return; var s = await SupabaseSecretsLoader.LoadAsync(); var opts = new SupabaseOptions { AutoConnectRealtime = false, AutoRefreshToken = true, Schema = "public" }; _client = new Supabase.Client(s.url, s.anonKey, opts); await _client.InitializeAsync(); } // ------------------------------------------------------------ // WalletConnected: check if exists, create if missing, store in PlayerPrefs // ------------------------------------------------------------ //public static async Task WalletConnectedAsync(string walletAddress) //{ // if (string.IsNullOrWhiteSpace(walletAddress)) // throw new ArgumentException("walletAddress cannot be empty."); // await InitAsync(); // // exists? // var existing = await _client.From() // .Select("*") // .Filter("wallet_address", Postgrest.Constants.Operator.Equals, walletAddress) // .Get(); // PlayerRecord result; // if (existing.Models.Count > 0) // { // result = existing.Models[0]; // } // else // { // // insert minimal row (DB supplies defaults & computed columns) // var insert = await _client.From().Insert(new PlayerRecord { WalletAddress = walletAddress }); // result = insert.Models.Count > 0 ? insert.Models[0] : new PlayerRecord { WalletAddress = walletAddress }; // } // // "login success": save wallet locally // PlayerPrefs.SetString(PrefsWalletKey, walletAddress); // PlayerPrefs.Save(); // return result; //} public static async Task WalletConnectedAsync(string walletAddress) { if (string.IsNullOrWhiteSpace(walletAddress)) throw new ArgumentException("walletAddress cannot be empty."); await InitAsync(); // Atomic, safe, and won’t try to write generated columns. var rows = await _client.Rpc>("ensure_player", new { p_wallet = walletAddress }); var player = rows != null && rows.Count > 0 ? rows[0] : null; if (player == null) throw new Exception("ensure_player RPC returned no row."); PlayerPrefs.SetString("WALLET_ADDRESS", walletAddress); PlayerPrefs.Save(); return player; } // ------------------------------------------------------------ // AddKill (+1 by default) -- uses RPC for atomic increment // ------------------------------------------------------------ public static async Task AddKillAsync(string walletAddress, int delta = 1) { await InitAsync(); var payload = new { p_wallet = walletAddress, p_delta = delta }; // SQL returns SETOF players → deserialize to a list/array and take first var rows = await _client.Rpc>("add_kills", payload); return rows != null && rows.Count > 0 ? rows[0] : null; } // ------------------------------------------------------------ // Call Supabase-side method to add currency (RPC) // ------------------------------------------------------------ public static async Task AddCurrencyAsync(string walletAddress, long delta) { await InitAsync(); var payload = new { p_wallet = walletAddress, p_delta = delta }; var rows = await _client.Rpc>("add_currency", payload); return rows != null && rows.Count > 0 ? rows[0] : null; } // ------------------------------------------------------------ // Get currency for player // ------------------------------------------------------------ public static async Task GetCurrencyAsync(string walletAddress) { await InitAsync(); var resp = await _client.From() .Select("in_game_currency") .Filter("wallet_address", Postgrest.Constants.Operator.Equals, walletAddress) .Get(); return resp.Models.Count > 0 ? resp.Models[0].InGameCurrency : 0L; } // ------------------------------------------------------------ // Get JSON for bought items // ------------------------------------------------------------ public static async Task GetPurchasedItemsJsonAsync(string walletAddress) { await InitAsync(); var resp = await _client.From() .Select("purchased_items") .Filter("wallet_address", Postgrest.Constants.Operator.Equals, walletAddress) .Get(); if (resp.Models.Count == 0) return "{}"; var dict = resp.Models[0].PurchasedItems ?? new Dictionary(); return JsonSerializer.Serialize(dict); } // ------------------------------------------------------------ // Edit JSON for bought items (replace with provided JSON) // ------------------------------------------------------------ public static async Task SetPurchasedItemsJsonAsync(string walletAddress, string json) { await InitAsync(); var rows = await _client.Rpc>( "set_purchased_items", new { p_wallet = walletAddress, p_items = System.Text.Json.JsonDocument.Parse(json).RootElement } ); return rows != null && rows.Count > 0 ? rows[0] : null; } // ------------------------------------------------------------ // +1 games played / +1 games won (RPCs) // ------------------------------------------------------------ public static async Task IncGamesPlayedAsync(string walletAddress) { await InitAsync(); var rows = await _client.Rpc>("inc_games_played", new { p_wallet = walletAddress }); return rows != null && rows.Count > 0 ? rows[0] : null; } public static async Task IncGamesWonAsync(string walletAddress) { await InitAsync(); var rows = await _client.Rpc>("inc_games_won", new { p_wallet = walletAddress }); return rows != null && rows.Count > 0 ? rows[0] : null; } // ------------------------------------------------------------ // Get Player data (full record) // ------------------------------------------------------------ public static async Task GetPlayerAsync(string walletAddress) { await InitAsync(); var resp = await _client.From() .Select("*") .Filter("wallet_address", Postgrest.Constants.Operator.Equals, walletAddress) .Get(); return resp.Models.Count > 0 ? resp.Models[0] : null; } public static async Task SendGameWinAsync( string walletAddress, int placement) { await InitAsync(); var payload = new { p_wallet = walletAddress, p_placement = placement }; var rows = await _client.Rpc>("record_game_win", payload); return rows != null && rows.Count > 0 ? rows[0] : null; } /// /// Add/overwrite one entry in purchased_items: key -> value (value default true). /// public static async Task AddPurchasedItemAsync(string walletAddress, string key, object value = null) { await InitAsync(); // Fallback: serialize -> parse -> take RootElement JsonElement jsonb = value == null ? JsonDocument.Parse("true").RootElement : JsonDocument.Parse(JsonSerializer.Serialize(value)).RootElement; var payload = new { p_wallet = walletAddress, p_key = key, p_value = jsonb }; var rows = await _client.Rpc>("add_purchased_item", payload); return rows != null && rows.Count > 0 ? rows[0] : null; } static string SanitizeName(string name) { if (string.IsNullOrWhiteSpace(name)) return null; name = name.Trim(); // allow letters, numbers, space, underscore, dash; clamp length 3..24 name = Regex.Replace(name, @"[^A-Za-z0-9 _\-]", ""); if (name.Length < 3) return null; if (name.Length > 24) name = name.Substring(0, 24); return name; } // -------- GETTER -------- public static async Task GetPlayerNameAsync(string walletAddress) { await InitAsync(); var resp = await _client.From() .Select("display_name") .Filter("wallet_address", Postgrest.Constants.Operator.Equals, walletAddress) .Get(); var name = resp.Models.Count > 0 ? resp.Models[0].DisplayName ?? "" : ""; // cache (optional) PlayerPrefs.SetString(PrefsName, name); PlayerPrefs.Save(); return name; } // -------- CHECK EMPTY (DB) -------- public static async Task IsNameEmptyAsync(string walletAddress) { var name = await GetPlayerNameAsync(walletAddress); return string.IsNullOrEmpty(name); } // -------- SETTER: only if empty (safe path) -------- // Calls your SQL function: set_name_if_empty(p_wallet, p_name) public static async Task SetNameIfEmptyAsync(string walletAddress, string proposedName) { await InitAsync(); var clean = SanitizeName(proposedName); if (string.IsNullOrEmpty(clean)) throw new ArgumentException("Invalid name. Use 3–24 chars: letters, numbers, space, _ or -."); // RPC returns the player row (whether it updated or not) var rows = await _client.Rpc>( "set_name_if_empty", new { p_wallet = walletAddress, p_name = clean } ); var player = rows != null && rows.Count > 0 ? rows[0] : null; if (player == null) throw new Exception("set_name_if_empty returned no row."); PlayerPrefs.SetString(PrefsName, player.DisplayName ?? ""); PlayerPrefs.Save(); return player; } // -------- SETTER: force update (optional, for rename UI) -------- public static async Task SetNameAsync(string walletAddress, string newName) { await InitAsync(); var clean = SanitizeName(newName); if (string.IsNullOrEmpty(clean)) throw new ArgumentException("Invalid name. Use 3–24 chars: letters, numbers, space, _ or -."); // RPC returns the row; no SDK .Set() nonsense, no risk of overwriting other fields var rows = await _client.Rpc>( "set_name", new { p_wallet = walletAddress, p_name = clean } ); var rec = rows != null && rows.Count > 0 ? rows[0] : null; if (rec == null) throw new Exception("Name update failed."); PlayerPrefs.SetString("DISPLAY_NAME", rec.DisplayName ?? ""); PlayerPrefs.Save(); return rec; } }