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

namespace NeonSprawl.Server.Game.Mastery;

/// <summary>Thread-safe in-memory perk state; seeds the configured dev player (NEO-47).</summary>
public sealed class InMemoryPlayerPerkStateStore(IOptions<GamePositionOptions> options) : IPlayerPerkStateStore
{
    private readonly ConcurrentDictionary<string, PlayerPerkState> byPlayer = CreateInitialMap(options.Value);

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

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

    /// <inheritdoc />
    public bool CanWritePlayer(string playerId)
    {
        var key = NormalizePlayerId(playerId);
        return key.Length > 0 && byPlayer.ContainsKey(key);
    }

    /// <inheritdoc />
    public PerkStateSnapshot GetSnapshot(string playerId)
    {
        var key = NormalizePlayerId(playerId);
        if (key.Length == 0 || !byPlayer.TryGetValue(key, out var inner))
        {
            return PerkStateSnapshot.Empty;
        }

        lock (playerLocks.GetOrAdd(key, _ => new object()))
        {
            return inner.ToSnapshot();
        }
    }

    /// <inheritdoc />
    public bool TrySetBranchPick(string playerId, string skillId, int tierIndex, string branchId)
    {
        var key = NormalizePlayerId(playerId);
        var sid = skillId.Trim();
        var bid = branchId.Trim();
        if (key.Length == 0 || sid.Length == 0 || bid.Length == 0 || tierIndex < 1
            || !byPlayer.TryGetValue(key, out var inner))
        {
            return false;
        }

        lock (playerLocks.GetOrAdd(key, _ => new object()))
        {
            if (inner.HasBranchPick(sid, tierIndex))
            {
                return false;
            }

            inner.SetBranchPick(sid, tierIndex, bid);
            return true;
        }
    }

    /// <inheritdoc />
    public bool TryAddUnlockedPerks(string playerId, IEnumerable<string> perkIds)
    {
        var key = NormalizePlayerId(playerId);
        if (key.Length == 0 || !byPlayer.TryGetValue(key, out var inner))
        {
            return false;
        }

        lock (playerLocks.GetOrAdd(key, _ => new object()))
        {
            foreach (var raw in perkIds)
            {
                var pid = raw.Trim();
                if (pid.Length == 0)
                {
                    continue;
                }

                inner.AddUnlockedPerk(pid);
            }

            return true;
        }
    }

    /// <inheritdoc />
    public bool TryRemoveBranchPick(string playerId, string skillId, int tierIndex)
    {
        var key = NormalizePlayerId(playerId);
        var sid = skillId.Trim();
        if (key.Length == 0 || sid.Length == 0 || tierIndex < 1 || !byPlayer.TryGetValue(key, out var inner))
        {
            return false;
        }

        lock (playerLocks.GetOrAdd(key, _ => new object()))
        {
            return inner.RemoveBranchPick(sid, tierIndex);
        }
    }

    /// <inheritdoc />
    public bool TryResetPerkState(string playerId)
    {
        var key = NormalizePlayerId(playerId);
        if (key.Length == 0 || !byPlayer.TryGetValue(key, out var inner))
        {
            return false;
        }

        lock (playerLocks.GetOrAdd(key, _ => new object()))
        {
            inner.Reset();
            return true;
        }
    }

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

        return t.ToLowerInvariant();
    }

    private sealed class PlayerPerkState
    {
        private readonly Dictionary<string, Dictionary<int, string>> branchPicks = new(StringComparer.Ordinal);

        private readonly HashSet<string> unlockedPerks = new(StringComparer.Ordinal);

        public bool HasBranchPick(string skillId, int tierIndex) =>
            branchPicks.TryGetValue(skillId, out var tiers) && tiers.ContainsKey(tierIndex);

        public void SetBranchPick(string skillId, int tierIndex, string branchId)
        {
            if (!branchPicks.TryGetValue(skillId, out var tiers))
            {
                tiers = new Dictionary<int, string>();
                branchPicks[skillId] = tiers;
            }

            tiers[tierIndex] = branchId;
        }

        public void AddUnlockedPerk(string perkId) => unlockedPerks.Add(perkId);

        public bool RemoveBranchPick(string skillId, int tierIndex) =>
            branchPicks.TryGetValue(skillId, out var tiers) && tiers.Remove(tierIndex);

        public void Reset()
        {
            branchPicks.Clear();
            unlockedPerks.Clear();
        }

        public PerkStateSnapshot ToSnapshot()
        {
            var bySkill = new Dictionary<string, IReadOnlyDictionary<int, string>>(StringComparer.Ordinal);
            foreach (var (skillId, tiers) in branchPicks)
            {
                bySkill[skillId] = new Dictionary<int, string>(tiers);
            }

            return new PerkStateSnapshot(bySkill, new HashSet<string>(unlockedPerks, StringComparer.Ordinal));
        }
    }
}
