using FishNet.Object;
using FishNet.Object.Synchronizing;
using UnityEngine;
using IStalk.Data;
using IStalk.Network;

namespace IStalk.Core
{
    public enum MatchState { WaitingForPlayers, GracePeriod, Hunt, Overtime, RunnersWin, EyeWins }

    public class GameManager : NetworkBehaviour
    {
        public static GameManager Instance { get; private set; }

        [SerializeField] private MapGenerator _mapGenerator;
        [SerializeField] private ExtractionManager _extractionManager;
        [SerializeField] private PlayerSpawner _playerSpawner;
        [SerializeField] private LootSpawner _lootSpawner;

        private readonly SyncVar<MatchState> _state = new();
        private readonly SyncVar<float> _matchTimer = new();
        private readonly SyncVar<float> _graceTimer = new();
        private readonly SyncVar<int> _sealedRoomCount = new();

        private GlobalRules_SO Rules => GlobalRules_SO.Instance;
        private int _totalRunners;
        private float _nextSealTime;

        public MatchState CurrentState => _state.Value;
        public float MatchTimer => _matchTimer.Value;
        public float GraceTimer => _graceTimer.Value;
        public int SealedRoomCount => _sealedRoomCount.Value;

        public event System.Action<MatchState> OnStateChanged;
        public event System.Action<int> OnRoomSealed;

        public override void OnStartNetwork()
        {
            base.OnStartNetwork();
            Instance = this;
        }

        public override void OnStartServer()
        {
            base.OnStartServer();
            _state.Value = MatchState.WaitingForPlayers;
            _state.OnChange += HandleStateChange;
            _extractionManager.OnAllExtracted += HandleAllExtracted;
            StartMatch();
        }

        public override void OnStartClient()
        {
            base.OnStartClient();
            _state.OnChange += HandleStateChange;
        }

        [Server]
        private void StartMatch()
        {
            _mapGenerator.Generate();
            _extractionManager.SetAltarPositions(new System.Collections.Generic.List<Vector3>(_mapGenerator.AltarPositions));

            if (_lootSpawner != null)
                _lootSpawner.SpawnLoot(_mapGenerator.LootSlotPositions);

            _mapGenerator.SpawnShrines();

            _totalRunners = Mathf.Max(1, GameStartData.PlayerCount - 1);
            _state.Value = MatchState.GracePeriod;
            _graceTimer.Value = Rules.gracePeriodDuration;
        }

        private void Update()
        {
            if (!IsServerStarted) return;

            switch (_state.Value)
            {
                case MatchState.GracePeriod:
                    UpdateGracePeriod();
                    break;
                case MatchState.Hunt:
                    UpdateHunt();
                    break;
                case MatchState.Overtime:
                    UpdateOvertime();
                    break;
            }
        }

        private void UpdateGracePeriod()
        {
            _graceTimer.Value -= Time.deltaTime;
            if (_graceTimer.Value <= 0)
            {
                _state.Value = MatchState.Hunt;
                _matchTimer.Value = Rules.matchTimeLimit;
            }
        }

        private void UpdateHunt()
        {
            _matchTimer.Value -= Time.deltaTime;
            if (_matchTimer.Value <= 0)
            {
                _state.Value = MatchState.Overtime;
                _nextSealTime = Rules.overtimeRoomSealRate;
            }
        }

        private void UpdateOvertime()
        {
            _matchTimer.Value -= Time.deltaTime;
            _nextSealTime -= Time.deltaTime;

            if (_nextSealTime <= 0)
            {
                _nextSealTime = Rules.overtimeRoomSealRate;
                SealNextRoom();
            }

            if (_sealedRoomCount.Value >= _mapGenerator.PlacedRooms.Count)
                _state.Value = MatchState.EyeWins;
        }

        [Server]
        private void SealNextRoom()
        {
            _sealedRoomCount.Value++;
            SealRoomObservers(_sealedRoomCount.Value);
            OnRoomSealed?.Invoke(_sealedRoomCount.Value);
        }

        [ObserversRpc]
        private void SealRoomObservers(int roomIndex)
        {
            OnRoomSealed?.Invoke(roomIndex);
        }

        private void HandleAllExtracted()
        {
            _state.Value = MatchState.RunnersWin;
        }

        [Server]
        public void ForceEyeWin()
        {
            _state.Value = MatchState.EyeWins;
        }

        private void HandleStateChange(MatchState prev, MatchState next, bool asServer)
        {
            OnStateChanged?.Invoke(next);
        }
    }
}
