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

namespace IStalk.Eye
{
    public class EyeLaserController : NetworkBehaviour
    {
        [SerializeField] private LineRenderer _telegraphLine;
        [SerializeField] private LineRenderer _laserLine;
        [SerializeField] private ParticleSystem _impactVfx;
        [SerializeField] private AudioSource _chargeAudio;
        [SerializeField] private AudioSource _fireAudio;
        [SerializeField] private LayerMask _hitLayers;

        private readonly SyncVar<LaserState> _state = new();
        private readonly SyncVar<Vector3> _aimStart = new();
        private readonly SyncVar<Vector3> _aimEnd = new();

        private float _chargeTimer;
        private float _cooldownTimer;
        private DreadEconomy _dread;

        private GlobalRules_SO Rules => GlobalRules_SO.Instance;

        private enum LaserState { Idle, Telegraphing, Charging, Firing, Cooldown }

        public override void OnStartClient()
        {
            base.OnStartClient();
            _state.OnChange += OnStateChanged;
            _aimEnd.OnChange += OnAimChanged;
            SetVisualsIdle();
        }

        public override void OnStartServer()
        {
            base.OnStartServer();
            _dread = GetComponent<DreadEconomy>();
        }

        private void Update()
        {
            if (IsOwner)
                HandleOwnerInput();

            if (IsClientStarted)
                UpdateVisuals();
        }

        private void HandleOwnerInput()
        {
            if (_cooldownTimer > 0)
            {
                _cooldownTimer -= Time.deltaTime;
                return;
            }

            if (Input.GetMouseButtonDown(0) && _state.Value == LaserState.Idle)
            {
                Vector3 worldPos = GetMouseWorldPosition();
                ServerStartTelegraph(worldPos);
            }

            if (Input.GetMouseButton(0) && _state.Value == LaserState.Telegraphing)
            {
                Vector3 worldPos = GetMouseWorldPosition();
                ServerUpdateAim(worldPos);
            }

            if (Input.GetMouseButtonUp(0) && _state.Value == LaserState.Telegraphing)
            {
                ServerFireLaser();
            }
        }

        [ServerRpc]
        private void ServerStartTelegraph(Vector3 targetPos)
        {
            _state.Value = LaserState.Telegraphing;
            _aimStart.Value = transform.position;
            _aimEnd.Value = targetPos;
            _chargeTimer = 0;
        }

        [ServerRpc]
        private void ServerUpdateAim(Vector3 targetPos)
        {
            _aimEnd.Value = targetPos;
        }

        [ServerRpc]
        private void ServerFireLaser()
        {
            _state.Value = LaserState.Firing;

            Vector3 dir = (_aimEnd.Value - _aimStart.Value).normalized;
            if (dir.sqrMagnitude < 0.01f) dir = Vector3.forward;

            if (Physics.SphereCast(_aimStart.Value, Rules.laserRadius, dir, out RaycastHit hit, Rules.laserRange, _hitLayers))
            {
                var runner = hit.collider.GetComponentInParent<Runner.RunnerController>();
                if (runner != null)
                    HandleRunnerHit(runner);
            }

            SetCooldownObservers(Rules.laserCooldown);
            Invoke(nameof(ResetToIdle), 0.3f);
        }

        [ObserversRpc]
        private void SetCooldownObservers(float duration)
        {
            _cooldownTimer = duration;
        }

        [Server]
        private void ResetToIdle()
        {
            _state.Value = LaserState.Idle;
        }

        [Server]
        private void HandleRunnerHit(Runner.RunnerController runner)
        {
            var health = runner.GetComponent<Runner.RunnerHealth>();
            if (health != null)
                health.TakeHit();
        }

        private void OnStateChanged(LaserState prev, LaserState next, bool asServer)
        {
            switch (next)
            {
                case LaserState.Idle:
                    SetVisualsIdle();
                    break;
                case LaserState.Telegraphing:
                    if (_chargeAudio != null) _chargeAudio.Play();
                    break;
                case LaserState.Firing:
                    if (_fireAudio != null) _fireAudio.Play();
                    break;
            }
        }

        private void OnAimChanged(Vector3 prev, Vector3 next, bool asServer)
        {
            UpdateVisuals();
        }

        private void UpdateVisuals()
        {
            switch (_state.Value)
            {
                case LaserState.Telegraphing:
                    if (_telegraphLine != null)
                    {
                        _telegraphLine.enabled = true;
                        _telegraphLine.SetPosition(0, _aimStart.Value);
                        _telegraphLine.SetPosition(1, _aimEnd.Value);
                    }
                    if (_laserLine != null) _laserLine.enabled = false;
                    break;

                case LaserState.Firing:
                    if (_telegraphLine != null) _telegraphLine.enabled = false;
                    if (_laserLine != null)
                    {
                        _laserLine.enabled = true;
                        _laserLine.SetPosition(0, _aimStart.Value);
                        _laserLine.SetPosition(1, _aimEnd.Value);
                    }
                    if (_impactVfx != null)
                    {
                        _impactVfx.transform.position = _aimEnd.Value;
                        _impactVfx.Play();
                    }
                    break;
            }
        }

        private void SetVisualsIdle()
        {
            if (_telegraphLine != null) _telegraphLine.enabled = false;
            if (_laserLine != null) _laserLine.enabled = false;
        }

        private Vector3 GetMouseWorldPosition()
        {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            Plane groundPlane = new(Vector3.up, Vector3.zero);
            if (groundPlane.Raycast(ray, out float dist))
                return ray.GetPoint(dist);
            return Vector3.zero;
        }
    }
}
