using System.Collections.Generic;
using Unity.Netcode;
using UnityEngine;

namespace TD.Gameplay
{
    /// <summary>
    /// Per-player gold pool. One instance is spawned per connected client (via
    /// <see cref="NetworkManager.PlayerPrefab"/>) and owned by that client.
    ///
    /// Replaces the earlier singleton-style GoldManager. Same server-authoritative
    /// pattern (NetworkVariable + server-validated Rpc), but every player has their
    /// own pool instead of sharing one.
    ///
    /// Three-beat pattern (unchanged from the original template):
    ///   1. State lives in NetworkVariables (server-only writes).
    ///   2. The owning client REQUESTS spends via a server-targeted Rpc.
    ///   3. The server VALIDATES before applying.
    /// </summary>
    /// <remarks>
    /// Lookup ergonomics: each instance registers itself in a static dictionary
    /// keyed by OwnerClientId during OnNetworkSpawn and removes itself during
    /// OnNetworkDespawn. Server code that needs to award gold to a specific
    /// player can call <see cref="GetForClient(ulong)"/>.
    ///
    /// This component is expected to live on the Player Prefab assigned to
    /// NetworkManager. NGO handles spawn-on-connect and ownership assignment
    /// automatically.
    /// </remarks>
    public class PlayerGoldManager : NetworkBehaviour
    {
        // --- Static registry ---------------------------------------------

        // Keyed by OwnerClientId. Populated on every peer when an instance
        // spawns; the same client can be looked up on the server (for awards)
        // and on clients (for UI). Kept private — access goes through GetForClient.
        private static readonly Dictionary<ulong, PlayerGoldManager> s_byClientId
            = new Dictionary<ulong, PlayerGoldManager>();

        /// <summary>
        /// Returns the PlayerGoldManager owned by the given client, or null if
        /// none is currently spawned. Safe to call on server or client.
        /// </summary>
        public static PlayerGoldManager GetForClient(ulong clientId)
        {
            s_byClientId.TryGetValue(clientId, out var manager);
            return manager;
        }

        /// <summary>
        /// Convenience: the local client's own gold manager. Returns null on a
        /// dedicated server or before the local player has spawned.
        /// </summary>
        public static PlayerGoldManager Local
        {
            get
            {
                var nm = NetworkManager.Singleton;
                if (nm == null || !nm.IsClient) return null;
                return GetForClient(nm.LocalClientId);
            }
        }

        // --- Tunables ----------------------------------------------------

        [Tooltip("Fallback starting gold used only when no GoldConfig is reachable at " +
                 "match start (e.g. editor testing in the Match scene without the lobby " +
                 "flow). Normally WaveManager overwrites this with GoldConfig.StartingGold " +
                 "during InitAfterSpawn.")]
        [SerializeField] private int startingGold = 100;

        // --- Networked state ---------------------------------------------

        // readPerm = Everyone so any client can read any other player's gold
        // (needed for scoreboard / opponent UI). writePerm = Server keeps
        // authority where it belongs.
        private readonly NetworkVariable<int> currentGold = new NetworkVariable<int>(
            value: 0,
            readPerm: NetworkVariableReadPermission.Everyone,
            writePerm: NetworkVariableWritePermission.Server
        );

        // Gold this player has accumulated since the start of the current wave. Reset to
        // 0 at the start of each new wave by WaveManager. Includes kill rewards,
        // completion bonus, and no-leak bonus — anything that flows through AwardGold.
        // Spending does NOT decrement it; it's a "gold earned", not "gold held", counter.
        // The HUD's top-bar "+N g/wave" reads this for the LOCAL player.
        private readonly NetworkVariable<int> goldEarnedThisWave = new NetworkVariable<int>(
            value: 0,
            readPerm: NetworkVariableReadPermission.Everyone,
            writePerm: NetworkVariableWritePermission.Server
        );

        public int CurrentGold => currentGold.Value;
        public int GoldEarnedThisWave => goldEarnedThisWave.Value;

        // --- Lifecycle ---------------------------------------------------

        public override void OnNetworkSpawn()
        {
            Debug.Log($"[PlayerGoldManager] OnNetworkSpawn. OwnerClientId={OwnerClientId}, " +
                      $"IsOwner={IsOwner}, IsServer={IsServer}");

            currentGold.OnValueChanged += HandleGoldChanged;
            s_byClientId[OwnerClientId] = this;

            if (IsServer)
            {
                currentGold.Value = startingGold;
            }
        }

        public override void OnNetworkDespawn()
        {
            currentGold.OnValueChanged -= HandleGoldChanged;

            // Only remove if the entry still points to this instance — guards
            // against the (unlikely) case where a new instance for the same
            // client has already overwritten the slot.
            if (s_byClientId.TryGetValue(OwnerClientId, out var registered) && registered == this)
            {
                s_byClientId.Remove(OwnerClientId);
            }
        }

        private void HandleGoldChanged(int previous, int current)
        {
            Debug.Log($"[PlayerGoldManager] Client {OwnerClientId} gold: {previous} -> {current}");
        }

        // --- Public API --------------------------------------------------

        /// <summary>
        /// Owning-client entry point for spending gold. Sends a server Rpc;
        /// the server validates and applies. Calling this on a non-owning
        /// client will be rejected by the Rpc's Owner permission.
        /// </summary>
        public void RequestSpendGold(int amount)
        {
            SpendGoldRpc(amount);
        }

        /// <summary>
        /// Server-side entry point for awarding gold (wave clear, enemy kill).
        /// Direct call — not Rpc-wrapped — because awards always originate
        /// from server-authoritative game events. Also increments
        /// <see cref="GoldEarnedThisWave"/> so the HUD's per-wave counter reflects
        /// it; spending does not decrement that counter (it tracks earnings, not balance).
        /// </summary>
        public void AwardGold(int amount)
        {
            if (!IsServer)
            {
                Debug.LogError("[PlayerGoldManager] AwardGold called on a client. " +
                               "Only server code should call this directly.");
                return;
            }

            if (amount <= 0) return;
            currentGold.Value += amount;
            goldEarnedThisWave.Value += amount;
        }

        /// <summary>
        /// Server-side: overwrites the player's current gold to the exact specified
        /// amount. Used by <c>WaveManager</c> at match start to apply
        /// <see cref="GoldConfig.StartingGold"/>, replacing the inspector-default value
        /// set during initial spawn. Does NOT touch <see cref="GoldEarnedThisWave"/>.
        /// </summary>
        public void ServerSetGold(int amount)
        {
            if (!IsServer)
            {
                Debug.LogError("[PlayerGoldManager] ServerSetGold called on a client. " +
                               "Only server code should call this directly.");
                return;
            }
            currentGold.Value = Mathf.Max(0, amount);
        }

        /// <summary>
        /// Server-side: resets <see cref="GoldEarnedThisWave"/> back to 0. Called by
        /// <c>WaveManager</c> at the start of each new wave so the HUD's top-bar
        /// per-wave counter starts fresh.
        /// </summary>
        public void ServerResetWaveEarnings()
        {
            if (!IsServer)
            {
                Debug.LogError("[PlayerGoldManager] ServerResetWaveEarnings called on a client.");
                return;
            }
            goldEarnedThisWave.Value = 0;
        }

        /// <summary>
        /// Server-side entry point for deducting gold (tower placement, other costs).
        /// Direct call — not Rpc-wrapped — because deductions always originate from
        /// server-authoritative validation (e.g., <c>TowerPlacementManager</c> after
        /// all checks have passed). Clamps to zero; gold cannot go negative.
        /// </summary>
        public void DeductGold(int amount)
        {
            if (!IsServer)
            {
                Debug.LogError("[PlayerGoldManager] DeductGold called on a client. " +
                               "Only server code should call this directly.");
                return;
            }

            if (amount <= 0) return;
            currentGold.Value = Mathf.Max(0, currentGold.Value - amount);
        }

        // --- Server-side Rpc ---------------------------------------------

        // InvokePermission = Owner: only the client that owns this NetworkObject
        // can invoke the Rpc. NGO will reject calls from anyone else, so a
        // malicious client can't spend another player's gold.
        //
        // This replaces the old "RequireOwnership = false" + the implicit
        // shared-pool semantics. The deprecation warning is gone.
        [Rpc(SendTo.Server, InvokePermission = RpcInvokePermission.Owner)]
        private void SpendGoldRpc(int amount, RpcParams rpcParams = default)
        {
            // Validation 1: positive amount.
            if (amount <= 0)
            {
                Debug.LogWarning($"[PlayerGoldManager] Rejected spend of {amount} " +
                                 $"from client {rpcParams.Receive.SenderClientId}: " +
                                 $"amount must be positive.");
                return;
            }

            // Validation 2: sufficient funds.
            if (currentGold.Value < amount)
            {
                Debug.LogWarning($"[PlayerGoldManager] Rejected spend of {amount} " +
                                 $"from client {rpcParams.Receive.SenderClientId}: " +
                                 $"insufficient funds (have {currentGold.Value}).");
                return;
            }

            currentGold.Value -= amount;
        }
    }
}