using EonaCat.Json;
using System;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

namespace EonaCat.Logger.Servers.Zabbix
{
    // This file is part of the EonaCat project(s) which is released under the Apache License.
    // See the LICENSE file or go to https://EonaCat.com/License for full license details.
    public class ZabbixRequest
    {
        private const int BUFFER_SIZE = 1024;

        /// <summary>
        /// The request to send to the Zabbix server
        /// </summary>
        public string Request { get; set; }

        /// <summary>
        /// The data to send to the Zabbix server
        /// </summary>
        public ZabbixData[] Data { get; set; }

        /// <summary>
        /// Zabbix request
        /// </summary>
        public ZabbixRequest(string host, string key, string value)
        {
            Request = "sender data";
            Data = new[] { new ZabbixData(host, key, value) };
        }

        /// <summary>
        /// Sends the request to the Zabbix server
        /// </summary>
        public async Task<ZabbixResponse> SendAsync(string server, int port = 10051, int timeout = 500)
        {
            if (string.IsNullOrWhiteSpace(server))
            {
                return new ZabbixResponse();
            }

            string json = JsonHelper.ToJson(new ZabbixRequest(Data[0].Host, Data[0].Key, Data[0].Value));

            using var tcpClient = new TcpClient();
            var connectTask = tcpClient.ConnectAsync(server, port);
            if (await Task.WhenAny(connectTask, Task.Delay(timeout)).ConfigureAwait(false) != connectTask)
            {
                throw new TimeoutException("Connection timed out.");
            }

            using NetworkStream stream = tcpClient.GetStream();
            stream.ReadTimeout = timeout;
            stream.WriteTimeout = timeout;

            // Header and length
            byte[] header = Encoding.ASCII.GetBytes("ZBXD\x01");
            byte[] dataLength = BitConverter.GetBytes((long)Encoding.ASCII.GetByteCount(json));
            if (!BitConverter.IsLittleEndian)
            {
                Array.Reverse(dataLength);
            }

            byte[] content = Encoding.ASCII.GetBytes(json);

            byte[] message = new byte[header.Length + dataLength.Length + content.Length];
            Buffer.BlockCopy(header, 0, message, 0, header.Length);
            Buffer.BlockCopy(dataLength, 0, message, header.Length, dataLength.Length);
            Buffer.BlockCopy(content, 0, message, header.Length + dataLength.Length, content.Length);

            await stream.WriteAsync(message, 0, message.Length).ConfigureAwait(false);
            await stream.FlushAsync().ConfigureAwait(false);

            // Wait for data available
            int counter = 0;
            while (!stream.DataAvailable)
            {
                if (counter < timeout / 50)
                {
                    counter++;
                    await Task.Delay(50).ConfigureAwait(false);
                }
                else
                {
                    throw new TimeoutException("No response received from Zabbix server.");
                }
            }

            // Read response safely
            using var memoryStream = new System.IO.MemoryStream();
            byte[] buffer = new byte[BUFFER_SIZE];
            int bytesRead;
            do
            {
                bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
                if (bytesRead > 0)
                {
                    memoryStream.Write(buffer, 0, bytesRead);
                }
            } while (bytesRead == buffer.Length);

            string responseAsString = Encoding.UTF8.GetString(memoryStream.ToArray());
            int jsonStart = responseAsString.IndexOf('{');
            if (jsonStart < 0)
            {
                return new ZabbixResponse();
            }

            string jsonResult = responseAsString.Substring(jsonStart);
            return JsonHelper.ToObject<ZabbixResponse>(jsonResult);
        }
    }
}
