// Assets/_Project/Scripts/Gameplay/EnemyHealth.cs
using System.Collections;
using Unity.Netcode;
using UnityEngine;
using TD.Core;

namespace TD.Gameplay
{
    /// <summary>
    /// Per-enemy HP component. Single point through which all damage flows so
    /// resistance lookups (Phase 1.5+) and kill attribution remain in one place.
    /// </summary>
    /// <remarks>
    /// <b>Initialization:</b> Call <see cref="InitializeServer"/> on the server
    /// immediately after <c>Instantiate</c> and before <c>NetworkObject.Spawn()</c>,
    /// following the same pattern as <c>TowerInstance.InitializeServer</c>.
    ///
    /// <b>Kill attribution:</b> <see cref="LastHitOwner"/> tracks the
    /// <see cref="PlayerSlot"/> of the tower that most recently dealt direct damage.
    /// DoT ticks from <c>EnemyStatus</c> also carry an owner so the credit
    /// follows the source tower, not the DoT applicator.
    ///
    /// <b>Death flow (server-only):</b> <see cref="TakeDamage"/> clamps HP to 0
    /// and fires <see cref="OnDied"/> immediately so wave bookkeeping and gold
    /// award happen the moment HP hits zero. The component then disables
    /// <see cref="EnemyMovement"/> and all child <c>Collider</c>s, triggers the
    /// <c>Die</c> animator parameter (synced via <c>NetworkAnimator</c>), waits
    /// <see cref="deathAnimationDuration"/> seconds, sinks the transform
    /// <see cref="sinkDepth"/> units over <see cref="sinkDuration"/> seconds,
    /// and finally calls <c>NetworkObject.Despawn</c>.
    /// </remarks>
    [RequireComponent(typeof(NetworkObject))]
    public class EnemyHealth : NetworkBehaviour, ISelectable
    {
        // ----- Inspector ------------------------------------------------------

        [Header("Identity")]
        [Tooltip("The EnemyDefinition this prefab represents. Drives the HUD info " +
                 "panel (name, speed, gold bounty) when the enemy is selected. " +
                 "Must be assigned on the prefab so it's available on every peer " +
                 "without needing a registry lookup.")]
        [SerializeField] private EnemyDefinition definition;

        /// <summary>The static definition this enemy was spawned from.</summary>
        public EnemyDefinition Definition => definition;

        [Header("Death sequence")]
        [Tooltip("Animator that plays the death animation. Trigger parameter 'Die' " +
                 "is set on death. Leave null to skip the death animation.")]
        [SerializeField] private Animator deathAnimator;

        [Tooltip("Seconds to hold after the Die trigger fires before the corpse starts sinking. " +
                 "Should match the death animation clip's duration.")]
        [SerializeField] private float deathAnimationDuration = 2f;

        [Tooltip("Seconds spent sinking into the ground.")]
        [SerializeField] private float sinkDuration = 1.5f;

        [Tooltip("How far (world units) the corpse sinks before despawning.")]
        [SerializeField] private float sinkDepth = 1.5f;

        private static readonly int DieHash = Animator.StringToHash("Die");

        // ----- Pre-spawn init (server-local) ----------------------------------

        private float     pendingMaxHp    = 100f;
        private int       pendingLivesCost = 1;
        private bool      pendingIsFlying;
        private bool      pendingIsHeld;
        private bool      hasPendingInit;

        // ----- Server-local runtime state -------------------------------------

        // Kill gold is no longer carried per-enemy — it comes from
        // GoldConfig.Waves[currentWaveIndex].GoldPerEnemy at the moment the kill
        // is registered. See WaveManager.HandleEnemyKilled.

        /// <summary>Lives deducted from the shared pool when this enemy reaches the goal.</summary>
        public int LivesCost { get; private set; } = 1;

        /// <summary>
        /// The <see cref="PlayerSlot"/> of the tower that last dealt direct damage.
        /// Used by <c>WaveManager</c> to award kill gold to the correct player.
        /// Updated on every <see cref="TakeDamage"/> call, including DoT ticks whose
        /// source owner is tracked on <see cref="EnemyStatus.StatusEffect"/>.
        /// </summary>
        public PlayerSlot LastHitOwner { get; private set; } = PlayerSlot.None;

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

        private readonly NetworkVariable<float> hp = new NetworkVariable<float>(
            0f,
            NetworkVariableReadPermission.Everyone,
            NetworkVariableWritePermission.Server);

        private readonly NetworkVariable<bool> isFlying = new NetworkVariable<bool>(
            false,
            NetworkVariableReadPermission.Everyone,
            NetworkVariableWritePermission.Server);

        private readonly NetworkVariable<bool> isHeld = new NetworkVariable<bool>(
            false,
            NetworkVariableReadPermission.Everyone,
            NetworkVariableWritePermission.Server);

        // ----- Public state ---------------------------------------------------

        public float CurrentHp => hp.Value;
        public float MaxHp     { get; private set; } = 100f;
        public bool  IsDead    => hp.Value <= 0f;
        public bool  IsHeld    => isHeld.Value;

        /// <summary>
        /// True if this enemy flies over tower footprints.
        /// Replicated so client visuals can adjust altitude.
        /// Grounded towers with GroundedOnly=true will not target flying enemies.
        /// </summary>
        public bool IsFlying => isFlying.Value;

        // ----- Events ---------------------------------------------------------

        /// <summary>
        /// Fired on the server immediately before the enemy NetworkObject is despawned.
        /// <c>WaveManager</c> subscribes to credit kill gold and decrement wave count.
        /// Do not access the NetworkObject after this event returns.
        /// </summary>
        public event System.Action<EnemyHealth> OnDied;

        // ----- Server-only pre-spawn init -------------------------------------

        /// <summary>
        /// Called by <c>WaveManager</c> on the server after <c>Instantiate</c>
        /// and before <c>NetworkObject.Spawn()</c>. Mirrors the
        /// <c>TowerInstance.InitializeServer</c> pattern.
        /// </summary>
        public void InitializeServer(float maxHp, int livesCost, bool flying, bool held = false)
        {
            pendingMaxHp      = maxHp;
            pendingLivesCost  = livesCost;
            pendingIsFlying   = flying;
            pendingIsHeld     = held;
            hasPendingInit    = true;

            // Cache locally on the server immediately — clients resolve via NV.
            MaxHp     = maxHp;
            LivesCost = livesCost;
        }

        // ----- NGO lifecycle --------------------------------------------------

        public override void OnNetworkSpawn()
        {
            if (IsServer && hasPendingInit)
            {
                hp.Value       = pendingMaxHp;
                isFlying.Value = pendingIsFlying;
                isHeld.Value   = pendingIsHeld;
                hasPendingInit = false;
            }

            // Non-server clients resolve MaxHp from the replicated hp initial value.
            if (!IsServer)
                MaxHp = hp.Value;

            // Apply initial held state on all peers and watch for future changes.
            // isHeld.OnValueChanged doesn't fire for the initial replication, so we
            // apply it explicitly here as well.
            ApplyHeldState(isHeld.Value);
            isHeld.OnValueChanged += (_, current) => ApplyHeldState(current);
        }

        public override void OnNetworkDespawn()
        {
            // If this enemy was the locally-selected ISelectable, clear the
            // selection so the HUD doesn't keep displaying a stale corpse.
            // SelectionState is a local UI singleton, safe to query on any peer.
            var sel = SelectionState.Instance;
            if (sel != null && sel.IsSelected(this))
                sel.Clear();
        }

        // ----- Server API -----------------------------------------------------

        /// <summary>
        /// Sets whether this enemy is held. Held enemies are untargetable and do not
        /// move. Server-only; the NetworkVariable replicates the change to all peers.
        /// </summary>
        public void SetHeld(bool held)
        {
            if (!IsServer) return;
            isHeld.Value = held;
        }

        private void ApplyHeldState(bool held)
        {
            if (IsDead) return;
            foreach (var col in GetComponentsInChildren<Collider>())
                col.enabled = !held;
        }

        /// <summary>
        /// Applies damage to this enemy. Server-only; silently no-ops on clients.
        /// <paramref name="type"/> is accepted for future resistance lookups (Phase 1.5+).
        /// <paramref name="attackerSlot"/> identifies the tower owner for kill attribution.
        /// </summary>
        public void TakeDamage(float damage, DamageType type, PlayerSlot attackerSlot)
        {
            if (!IsServer) return;
            if (IsDead)    return;

            // STUB — resistance table slot:
            // float modified = ResistanceTable.Apply(damage, type, this);
            float modified = damage;

            LastHitOwner = attackerSlot;
            hp.Value     = Mathf.Max(0f, hp.Value - modified);

            if (hp.Value <= 0f)
                HandleDeath();
        }

        // ----- Private --------------------------------------------------------

        private void HandleDeath()
        {
            // Fire OnDied immediately so the wave count decrements and gold is
            // awarded the moment HP hits zero — the corpse animation and sink
            // play out asynchronously after that.
            OnDied?.Invoke(this);

            // Stop pathing and remove from tower targeting / selection.
            var movement = GetComponent<EnemyMovement>();
            if (movement != null) movement.enabled = false;

            foreach (var col in GetComponentsInChildren<Collider>())
                col.enabled = false;

            // Trigger the death animation. NetworkAnimator on the prefab syncs the
            // Die trigger to all clients automatically — no ClientRpc needed.
            if (deathAnimator != null)
                deathAnimator.SetTrigger(DieHash);

            StartCoroutine(DeathSequence());
        }

        // ----- ISelectable ----------------------------------------------------

        /// <summary>
        /// Enemy display name. Pulls from <see cref="definition"/> when assigned;
        /// falls back to "Enemy" so the HUD never shows an empty portrait label
        /// even if a prefab is missing its definition reference.
        /// </summary>
        public string DisplayName =>
            definition != null && !string.IsNullOrEmpty(definition.DisplayName)
                ? definition.DisplayName
                : "Enemy";

        public SelectableKind Kind => SelectableKind.Enemy;
        public Transform      SelectionTransform => transform;

        // Used by SelectionVisualizer to size the selection ring. Tuned small
        // because enemy capsules are roughly 0.6-0.8 wide; if you change enemy
        // mesh sizes substantially, derive this from a collider bounds instead.
        public float SelectionRadius => 0.4f;

        private IEnumerator DeathSequence()
        {
            // Hold while the death animation plays.
            yield return new WaitForSeconds(deathAnimationDuration);

            // Sink phase. Server moves the transform; NetworkTransform replicates it.
            float t = 0f;
            Vector3 startPos = transform.position;
            Vector3 endPos   = startPos + Vector3.down * sinkDepth;

            while (t < sinkDuration)
            {
                t += Time.deltaTime;
                transform.position = Vector3.Lerp(startPos, endPos, t / sinkDuration);
                yield return null;
            }

            if (NetworkObject != null && NetworkObject.IsSpawned)
                NetworkObject.Despawn();
        }
    }
}
