using System;
using System.Collections.Generic;
using FishNet.Managing;
using FishNet.Transporting;
using UnityEngine;

namespace IStalk.Network
{
    public class RelayTransport : Transport
    {
        private LocalConnectionState _clientState = LocalConnectionState.Stopped;
        private LocalConnectionState _serverState = LocalConnectionState.Stopped;

        private readonly Queue<(byte[] data, bool isServer)> _incomingQueue = new();

        public override void Initialize(NetworkManager networkManager, int transportIndex)
        {
            base.Initialize(networkManager, transportIndex);
            RelayClient.Instance.OnGamePacket += HandleIncomingPacket;
            RelayClient.Instance.OnDisconnected += HandleDisconnect;
        }

        private void OnDestroy()
        {
            if (RelayClient.Instance != null)
            {
                RelayClient.Instance.OnGamePacket -= HandleIncomingPacket;
                RelayClient.Instance.OnDisconnected -= HandleDisconnect;
            }
        }

        public override string GetConnectionAddress(int connectionId) => "relay";
        public override int GetMTU(byte channel) => 1200;

        public override event Action<ClientConnectionStateArgs> OnClientConnectionState;
        public override event Action<ServerConnectionStateArgs> OnServerConnectionState;
        public override event Action<RemoteConnectionStateArgs> OnRemoteConnectionState;
        public override event Action<ClientReceivedDataArgs> OnClientReceivedData;
        public override event Action<ServerReceivedDataArgs> OnServerReceivedData;

        public override LocalConnectionState GetConnectionState(bool server)
        {
            return server ? _serverState : _clientState;
        }

        public override RemoteConnectionState GetConnectionState(int connectionId)
        {
            return RemoteConnectionState.Started;
        }

        public override bool StartConnection(bool server)
        {
            if (server)
            {
                SetServerState(LocalConnectionState.Starting);
                SetServerState(LocalConnectionState.Started);
            }
            else
            {
                SetClientState(LocalConnectionState.Starting);
                SetClientState(LocalConnectionState.Started);
            }
            return true;
        }

        public override bool StopConnection(bool server)
        {
            if (server)
            {
                SetServerState(LocalConnectionState.Stopping);
                SetServerState(LocalConnectionState.Stopped);
            }
            else
            {
                SetClientState(LocalConnectionState.Stopping);
                SetClientState(LocalConnectionState.Stopped);
            }
            return true;
        }

        public override bool StopConnection(int connectionId, bool immediately)
        {
            return true;
        }

        public override void Shutdown()
        {
            StopConnection(true);
            StopConnection(false);
            _incomingQueue.Clear();
        }

        public override void SendToServer(byte channelId, ArraySegment<byte> segment)
        {
            byte[] packet = new byte[segment.Count + 1];
            packet[0] = channelId;
            Buffer.BlockCopy(segment.Array, segment.Offset, packet, 1, segment.Count);
            RelayClient.Instance.SendGamePacket(packet);
        }

        public override void SendToClient(byte channelId, ArraySegment<byte> segment, int connectionId)
        {
            byte[] packet = new byte[segment.Count + 1];
            packet[0] = channelId;
            Buffer.BlockCopy(segment.Array, segment.Offset, packet, 1, segment.Count);
            RelayClient.Instance.SendGamePacket(packet);
        }

        public override void IterateIncoming(bool server)
        {
            while (_incomingQueue.Count > 0)
            {
                var (data, forServer) = _incomingQueue.Peek();
                if (forServer != server) break;
                _incomingQueue.Dequeue();
                ProcessPacket(data, server);
            }
        }

        public override void IterateOutgoing(bool server) { }

        public override void HandleClientConnectionState(ClientConnectionStateArgs connectionStateArgs)
        {
            OnClientConnectionState?.Invoke(connectionStateArgs);
        }

        public override void HandleServerConnectionState(ServerConnectionStateArgs connectionStateArgs)
        {
            OnServerConnectionState?.Invoke(connectionStateArgs);
        }

        public override void HandleRemoteConnectionState(RemoteConnectionStateArgs connectionStateArgs)
        {
            OnRemoteConnectionState?.Invoke(connectionStateArgs);
        }

        public override void HandleClientReceivedDataArgs(ClientReceivedDataArgs receivedDataArgs)
        {
            OnClientReceivedData?.Invoke(receivedDataArgs);
        }

        public override void HandleServerReceivedDataArgs(ServerReceivedDataArgs receivedDataArgs)
        {
            OnServerReceivedData?.Invoke(receivedDataArgs);
        }

        private void HandleIncomingPacket(byte[] data)
        {
            if (data.Length < 2) return;
            _incomingQueue.Enqueue((data, GameStartData.IsHost));
        }

        private void ProcessPacket(byte[] data, bool asServer)
        {
            byte channelId = data[0];
            Channel channel = (Channel)channelId;
            ArraySegment<byte> segment = new(data, 1, data.Length - 1);

            if (asServer)
            {
                OnServerReceivedData?.Invoke(new ServerReceivedDataArgs(segment, channel, 1, Index));
            }
            else
            {
                OnClientReceivedData?.Invoke(new ClientReceivedDataArgs(segment, channel, Index));
            }
        }

        private void HandleDisconnect()
        {
            if (_clientState == LocalConnectionState.Started)
                StopConnection(false);
            if (_serverState == LocalConnectionState.Started)
                StopConnection(true);
        }

        private void SetClientState(LocalConnectionState state)
        {
            _clientState = state;
            OnClientConnectionState?.Invoke(new ClientConnectionStateArgs(state, Index));
        }

        private void SetServerState(LocalConnectionState state)
        {
            _serverState = state;
            OnServerConnectionState?.Invoke(new ServerConnectionStateArgs(state, Index));
        }
    }
}
