using System.Collections.Generic;
using FishNet.Object;
using FishNet.Object.Synchronizing;
using UnityEngine;
using IStalk.Data;

namespace IStalk.Core
{
    public class ExtractionRoom : NetworkBehaviour
    {
        [SerializeField] private Collider _roomTrigger;

        private readonly SyncVar<bool> _isUnlocked = new();
        private readonly SyncVar<float> _extractionTimer = new(new SyncTypeSettings(0.5f));
        private readonly SyncVar<int> _runnersInsideCount = new();

        private readonly HashSet<NetworkObject> _runnersInside = new();
        private ExtractionManager _extractionManager;

        public bool IsUnlocked => _isUnlocked.Value;
        public float Timer => _extractionTimer.Value;
        public int RunnersInside => _runnersInsideCount.Value;

        public event System.Action OnExtractionStarted;
        public event System.Action OnExtractionComplete;
        public event System.Action OnExtractionInterrupted;

        public override void OnStartServer()
        {
            base.OnStartServer();
            _extractionManager = FindAnyObjectByType<ExtractionManager>();
            _extractionTimer.Value = GlobalRules_SO.Instance.extractionChannelDuration;
        }

        public override void OnStartClient()
        {
            base.OnStartClient();
            _extractionTimer.OnChange += OnTimerChanged;
        }

        [Server]
        public void Unlock()
        {
            _isUnlocked.Value = true;
            UnlockDoorObservers();
        }

        [ObserversRpc]
        private void UnlockDoorObservers()
        {
        }

        private void OnTriggerEnter(Collider other)
        {
            if (!IsServerInitialized || !_isUnlocked.Value) return;
            var nob = other.GetComponentInParent<NetworkObject>();
            if (nob == null) return;
            var health = nob.GetComponent<Runner.RunnerHealth>();
            if (health == null || health.IsDown) return;

            _runnersInside.Add(nob);
            _runnersInsideCount.Value = _runnersInside.Count;
        }

        private void OnTriggerExit(Collider other)
        {
            if (!IsServerInitialized) return;
            var nob = other.GetComponentInParent<NetworkObject>();
            if (nob == null) return;

            if (_runnersInside.Remove(nob))
            {
                _runnersInsideCount.Value = _runnersInside.Count;
                if (_extractionTimer.Value < GlobalRules_SO.Instance.extractionChannelDuration)
                {
                    _extractionTimer.Value = GlobalRules_SO.Instance.extractionChannelDuration;
                    ExtractionInterruptedObservers();
                }
            }
        }

        private void Update()
        {
            if (!IsServerInitialized || !_isUnlocked.Value) return;

            CleanupDownedRunners();

            int aliveCount = _extractionManager != null ? _extractionManager.AliveRunnerCount : 0;
            if (aliveCount <= 0) return;

            if (_runnersInside.Count >= aliveCount && _runnersInside.Count > 0)
            {
                _extractionTimer.Value -= Time.deltaTime;
                if (_extractionTimer.Value <= 0)
                {
                    _extractionTimer.Value = 0;
                    CompleteExtraction();
                }
            }
            else
            {
                if (_extractionTimer.Value < GlobalRules_SO.Instance.extractionChannelDuration)
                {
                    _extractionTimer.Value = GlobalRules_SO.Instance.extractionChannelDuration;
                    ExtractionInterruptedObservers();
                }
            }
        }

        private void CleanupDownedRunners()
        {
            var toRemove = new List<NetworkObject>();
            foreach (var nob in _runnersInside)
            {
                if (nob == null)
                {
                    toRemove.Add(nob);
                    continue;
                }
                var health = nob.GetComponent<Runner.RunnerHealth>();
                if (health != null && health.IsDown)
                    toRemove.Add(nob);
            }
            foreach (var nob in toRemove)
                _runnersInside.Remove(nob);

            if (toRemove.Count > 0)
                _runnersInsideCount.Value = _runnersInside.Count;
        }

        [Server]
        private void CompleteExtraction()
        {
            if (_extractionManager != null)
                _extractionManager.ExtractAll();
            ExtractionCompleteObservers();
        }

        [ObserversRpc]
        private void ExtractionCompleteObservers()
        {
            OnExtractionComplete?.Invoke();
        }

        [ObserversRpc]
        private void ExtractionInterruptedObservers()
        {
            OnExtractionInterrupted?.Invoke();
        }

        private void OnTimerChanged(float prev, float next, bool asServer)
        {
            if (!asServer && next < prev && prev >= GlobalRules_SO.Instance.extractionChannelDuration)
                OnExtractionStarted?.Invoke();
        }
    }
}
