using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System.Security.Cryptography;
using DorkNet.Server.Auth;
using DorkNet.Server.Data;
using DorkNet.Server.Data.Entities;
namespace DorkNet.Server.Controllers.API.ItemWishlists;
///
/// api.rec.net/api/itemWishlists — consumable/gift-drop wishlist
/// (RecNet.Runtime BPAIBFBKKCN). Wire contract verified against the
/// 2023.03.21 ISIL decompile:
///
/// GET api/itemWishlists/v1/wishlist/{accountId|me}
/// - BPAIBFBKKCN.txt:700-763 (ELPBIBPOBJF) — URL is
/// String.Concat("api/itemWishlists/v1/wishlist/", idOrMe) where
/// idOrMe is "me" for the local account, else the numeric account
/// id as a PATH SEGMENT (not a query param). Missing this route
/// 404s with an empty body → CHGIJBGGJAG "Response was empty" →
/// "Failed to get wishlist for player" (Player.log:2832-2872).
/// - Return type FGLDKEJLAKB<List<BFJNGMGONED>> with no converter
/// Func → the body is a BARE ARRAY of BFJNGMGONED.
/// - BFJNGMGONED = { Guid WishlistItemId, Int32 AccountId,
/// Int32 PurchasableItemId, DateTime CreatedAt }
/// (property types BFJNGMGONED.txt:3-97; JSON keys registered by
/// reader MCDADMNCDEI.txt:343-434, Pascal/camel/lower probes).
///
/// PUT api/itemWishlists/v1/wishlist/me/{purchasableItemId}
/// - BPAIBFBKKCN.txt:161-215 (OOPPFFPOAFH) — format string
/// "{0}/v1/wishlist/me/{1}", verb field = 3 = PUT
/// (verb table HNLCIDLIIBO.txt:878-903). Response is parsed as the
/// AECMPGPHAII<BFJNGMGONED> envelope {"Value","Success","Error"}
/// (envelope reader keys LEEAJGDIOHI.txt:243-286).
///
/// DELETE api/itemWishlists/v1/wishlist/me/{purchasableItemId}
/// - BPAIBFBKKCN.txt:375-419 (IBLKDCAIODF) — same URL, verb 4 =
/// DELETE, fire-and-forget (LDGADANDBIO) — body ignored.
///
/// Storage: rows live in ItemWishlists with the purchasable item id
/// serialized into the existing ItemKey column (no schema change);
/// WishlistItemId is derived deterministically from the row id.
///
[ApiController]
[Authorize]
public class ItemWishlistsController(DorkNetDbContext db) : ControllerBase
{
private long Me => this.RequireCurrentPlayerId();
[HttpGet("api/itemWishlists/v1/wishlist/me")]
public async Task Mine() => await ForPlayer(Me);
[HttpGet("api/itemWishlists/v1/wishlist/{accountId:long}")]
public async Task ForPlayer(long accountId)
{
var rows = await db.ItemWishlists
.Where(w => w.PlayerId == accountId)
.OrderByDescending(w => w.CreatedAt)
.ToListAsync();
return Ok(rows.Select(ToWire).Where(w => w is not null).ToArray());
}
[HttpPut("api/itemWishlists/v1/wishlist/me/{purchasableItemId:int}")]
[HttpPost("api/itemWishlists/v1/wishlist/me/{purchasableItemId:int}")]
public async Task Add(int purchasableItemId)
{
var key = purchasableItemId.ToString();
var row = await db.ItemWishlists.FirstOrDefaultAsync(w =>
w.PlayerId == Me && w.ItemKey == key);
if (row is null)
{
row = new ItemWishlistEntity
{
PlayerId = Me,
ItemKey = key,
ItemType = 0,
};
db.ItemWishlists.Add(row);
await db.SaveChangesAsync();
}
// AECMPGPHAII envelope: {"Value","Success","Error"}
// (LEEAJGDIOHI.txt:243-286).
return Ok(new
{
Value = ToWire(row),
Success = true,
Error = (string?)null,
});
}
[HttpDelete("api/itemWishlists/v1/wishlist/me/{purchasableItemId:int}")]
public async Task Remove(int purchasableItemId)
{
var key = purchasableItemId.ToString();
await db.ItemWishlists
.Where(w => w.PlayerId == Me && w.ItemKey == key)
.ExecuteDeleteAsync();
// Fire-and-forget on the client (LDGADANDBIO) — body ignored.
return Ok(new { Success = true });
}
/// BFJNGMGONED wire item. Rows whose ItemKey isn't a
/// purchasable-item integer (legacy free-form keys) are skipped.
private static object? ToWire(ItemWishlistEntity row)
{
if (!int.TryParse(row.ItemKey, out var purchasableItemId)) return null;
return new
{
WishlistItemId = StableGuid(row.Id, row.PlayerId),
AccountId = (int)row.PlayerId,
PurchasableItemId = purchasableItemId,
row.CreatedAt,
};
}
/// Deterministic per-row Guid — the client only uses
/// WishlistItemId as an opaque identity, but it must stay stable
/// across requests.
private static Guid StableGuid(long rowId, long playerId)
{
Span input = stackalloc byte[16];
BitConverter.TryWriteBytes(input, rowId);
BitConverter.TryWriteBytes(input[8..], playerId);
Span hash = stackalloc byte[32];
SHA256.HashData(input, hash);
return new Guid(hash[..16]);
}
}