using System;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using Timer = System.Threading.Timer;

namespace B24SiemensEmulator
{
    public class SiemensTcpClient : IDisposable
    {
        private TcpClient? _tcpClient;
        private NetworkStream? _stream;
        private Timer? _sendTimer;
        private bool _isConnected = false;
        private bool _isRunning = false;
        private readonly object _lock = new object();

        public event Action<bool>? ConnectionStatusChanged;
        public event Action<PLCPacket>? ResponseReceived;
        public event Action<string>? ErrorOccurred;

        private readonly string _serverHost = "localhost";
        private readonly int _serverPort = 42010;
        private readonly int _sendIntervalMs = 1000;

        public Func<PLCPacket>? GetPacketToSend;

        public bool IsConnected => _isConnected;
        public bool IsRunning => _isRunning;

        public async Task<bool> ConnectAsync()
        {
            try
            {
                if (_isConnected)
                    return true;

                _tcpClient = new TcpClient();
                await _tcpClient.ConnectAsync(_serverHost, _serverPort);
                _stream = _tcpClient.GetStream();

                lock (_lock)
                {
                    _isConnected = true;
                }

                ConnectionStatusChanged?.Invoke(true);
                return true;
            }
            catch (Exception ex)
            {
                ErrorOccurred?.Invoke($"Connection failed: {ex.Message}");
                await DisconnectAsync();
                return false;
            }
        }

        public async Task DisconnectAsync()
        {
            lock (_lock)
            {
                _isConnected = false;
            }

            _stream?.Close();
            _stream?.Dispose();
            _stream = null;

            _tcpClient?.Close();
            _tcpClient?.Dispose();
            _tcpClient = null;

            ConnectionStatusChanged?.Invoke(false);
        }

        public void StartSending()
        {
            if (_isRunning)
                return;

            _isRunning = true;
            _sendTimer = new Timer(SendPacketCallback, null, 0, _sendIntervalMs);
        }

        public void StopSending()
        {
            _isRunning = false;
            _sendTimer?.Dispose();
            _sendTimer = null;
        }

        private async void SendPacketCallback(object? state)
        {
            if (!_isConnected || GetPacketToSend == null)
                return;

            try
            {
                var packet = GetPacketToSend();
                await SendPacketAsync(packet);
            }
            catch (Exception ex)
            {
                ErrorOccurred?.Invoke($"Send failed: {ex.Message}");
                await HandleConnectionLoss();
            }
        }

        private async Task SendPacketAsync(PLCPacket packet)
        {
            if (_stream == null || !_isConnected)
                throw new InvalidOperationException("Not connected");

            var data = packet.Serialize();
            await _stream.WriteAsync(data, 0, data.Length);

            // Read response
            var responseBuffer = new byte[19]; // PLCPacket is 19 bytes
            int totalBytesRead = 0;
            
            while (totalBytesRead < responseBuffer.Length)
            {
                int bytesRead = await _stream.ReadAsync(responseBuffer, totalBytesRead, 
                    responseBuffer.Length - totalBytesRead);
                
                if (bytesRead == 0)
                    throw new Exception("Server disconnected");
                
                totalBytesRead += bytesRead;
            }

            var responsePacket = PLCPacket.Deserialize(responseBuffer);
            ResponseReceived?.Invoke(responsePacket);
        }

        private async Task HandleConnectionLoss()
        {
            await DisconnectAsync();
            
            // Wait 1 second before trying to reconnect
            await Task.Delay(1000);
            
            if (_isRunning)
            {
                var reconnected = await ConnectAsync();
                if (!reconnected)
                {
                    // Schedule another reconnection attempt
                    _ = Task.Run(async () =>
                    {
                        await Task.Delay(1000);
                        if (_isRunning && !_isConnected)
                        {
                            await HandleConnectionLoss();
                        }
                    });
                }
            }
        }

        public void Dispose()
        {
            StopSending();
            DisconnectAsync().Wait(1000);
        }
    }
}