using System.Collections.Concurrent;
using Microsoft.Extensions.Options;
using NeonSprawl.Server.Game.PositionState;

namespace NeonSprawl.Server.Game.Items;

/// <summary>Thread-safe in-memory inventory; seeds configured dev player with an empty snapshot (NEO-54).</summary>
public sealed class InMemoryPlayerInventoryStore(IOptions<GamePositionOptions> options) : IPlayerInventoryStore
{
    private readonly ConcurrentDictionary<string, PlayerInventorySnapshot> byPlayer = CreateInitialMap(options.Value);

    private readonly ConcurrentDictionary<string, object> playerLocks = new(StringComparer.OrdinalIgnoreCase);

    private static ConcurrentDictionary<string, PlayerInventorySnapshot> CreateInitialMap(GamePositionOptions o)
    {
        var id = NormalizePlayerId(o.DevPlayerId);
        var map = new ConcurrentDictionary<string, PlayerInventorySnapshot>(StringComparer.OrdinalIgnoreCase);
        map[id] = PlayerInventorySnapshot.Empty();
        return map;
    }

    /// <inheritdoc />
    public bool TryGetSnapshot(string playerId, out PlayerInventorySnapshot snapshot)
    {
        var key = NormalizePlayerId(playerId);
        if (key.Length == 0)
        {
            snapshot = PlayerInventorySnapshot.Empty();
            return false;
        }

        lock (playerLocks.GetOrAdd(key, _ => new object()))
        {
            if (!byPlayer.TryGetValue(key, out var current))
            {
                snapshot = PlayerInventorySnapshot.Empty();
                return false;
            }

            snapshot = Clone(current);
            return true;
        }
    }

    /// <inheritdoc />
    public bool TryReplaceSnapshot(string playerId, PlayerInventorySnapshot snapshot)
    {
        var key = NormalizePlayerId(playerId);
        if (key.Length == 0)
        {
            return false;
        }

        lock (playerLocks.GetOrAdd(key, _ => new object()))
        {
            if (!byPlayer.ContainsKey(key))
            {
                return false;
            }

            byPlayer[key] = Clone(snapshot);
            return true;
        }
    }

    /// <inheritdoc />
    public bool TryMutateSnapshot(
        string playerId,
        Func<PlayerInventorySnapshot, PlayerInventoryMutationWrite> mutator,
        out PlayerInventorySnapshot result)
    {
        var key = NormalizePlayerId(playerId);
        if (key.Length == 0)
        {
            result = PlayerInventorySnapshot.Empty();
            return false;
        }

        lock (playerLocks.GetOrAdd(key, _ => new object()))
        {
            if (!byPlayer.TryGetValue(key, out var current))
            {
                result = PlayerInventorySnapshot.Empty();
                return false;
            }

            var write = mutator(Clone(current));
            result = Clone(write.Value);
            if (write.Write)
            {
                byPlayer[key] = Clone(write.Value);
            }

            return true;
        }
    }

    private static PlayerInventorySnapshot Clone(PlayerInventorySnapshot source) =>
        new()
        {
            BagSlots = PlayerInventorySnapshot.CloneSlots(source.BagSlots),
            EquipmentSlots = PlayerInventorySnapshot.CloneSlots(source.EquipmentSlots),
        };

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

        return t.ToLowerInvariant();
    }
}
