using System.Net.Sockets;
using System.Text;
using adas_core.Domain.Enums;
using adas_core.Domain.Models.MongoModels;
using adas_core.module.Relays.Models;
using Serilog;

namespace adas_core.module.Relays.Devices;

/// <summary>
/// Class for controlling a KMTronic relay device.
/// </summary>
/// <param name="relay">The Relay object representing the relay device.</param>
/// <param name="relaySettings">The RelaySettings object containing the configuration settings for the relay device.</param>
public class KmTronicRelay(Relay relay, RelaySettings relaySettings)
    : RelayDevice(relay, relaySettings)
{
    /// <summary>
    /// UDP client used for communication with the KMTronic relay device. It is initialized when connecting to the device and is used to send commands and receive responses.
    /// </summary>
    private UdpClient? _udpClient;

    /// <summary>
    /// Indicates the current status of the KMTronic relay device. It is updated by the CheckStatus method and can be used to determine if the device is online or offline.
    /// </summary>
    public bool DeviceStatus { get; set; }

    /// <summary>
    /// Checks the connectivity and status of the KMTronic relay device by sending a command to retrieve the status of the first outlet. It updates the DeviceStatus property based on the response received from the device. 
    /// If the response indicates that the status is unknown, it returns false, indicating that the device is not reachable or not responding correctly. Otherwise, it returns true, indicating that the device is online and responsive.
    /// </summary>
    /// <returns>True if the device is online and responsive, false otherwise.</returns>
    public override bool CheckDevice()
    {
        CheckStatus();
        var result = GetStatusRelay(1);
        return result != RelayEnum.Status.Unknown;
    }

    /// <summary>
    /// Retrieves the status of the KMTronic relay device by sending a command to get the status of all outlets. It checks if the response is not null or empty, which indicates that the device is online and responsive. 
    /// If the response is null or empty, it indicates that the device is not reachable or not responding correctly, and it returns false. Otherwise, it returns true, indicating that the device is online and responsive.
    /// </summary>
    /// <returns>True if the device is online and responsive, false otherwise.</returns>
    public override bool GetStatusRelay()
    {
        var status = Send("FF0000");
        //while (!status.IsCompleted)
        //{

        //}
        //return !string.IsNullOrEmpty(status.Result);

        return !string.IsNullOrEmpty(status);
    }

    /// <summary>
    /// Retrieves the status of a specific outlet on the KMTronic relay device by sending a command to get the status of all outlets. 
    /// It checks if the response is not null or empty, and if it contains the expected format for the outlet status.
    /// </summary>
    /// <param name="outletId">The ID of the outlet to retrieve the status for.</param>
    /// <returns>The status of the specified outlet.</returns>
    public override RelayEnum.Status GetStatusRelay(int outletId)
    {
        var status = Send("FF0000");

        //while (!status.IsCompleted)
        //{

        //}


        //if (!string.IsNullOrEmpty(status.Result))
        //{
        //    if (status.Result.Substring(outlet.outlet - 1, 1) == "0")
        //    {
        //        return true;
        //    }
        //}

        if (!string.IsNullOrEmpty(status))
            try
            {
                if (status.Substring(outletId - 1, 1) == "0")
                    //return RelayStatus.Off;
                    return Relay.Mode == RelayEnum.Mode.OpenedOffClosedOn
                        ? RelayEnum.Status.On
                        : RelayEnum.Status.Off;
            }
            catch (ArgumentOutOfRangeException e)
            {
                Log.Error(
                    "There isn't an outlet {Outlet} in the rele {Ip}. Please check the configuration.\nException: {Ex}",
                    outletId, Relay.Ip, e.Message);
            }

        //return RelayStatus.On;
        return Relay.Mode == RelayEnum.Mode.OpenedOffClosedOn ? RelayEnum.Status.Off : RelayEnum.Status.On;
    }

    /// <summary>
    /// Powers off a specific outlet on the KMTronic relay device by sending a command to either open or close the relay, depending on the configured mode.
    /// </summary>
    /// <param name="outletId">The ID of the outlet to power off.</param>
    public override void PowerOffRelay(int outletId)
    {
        if (Relay.Mode == RelayEnum.Mode.OpenedOffClosedOn) OpenRelay(outletId);
        else CloseRelay(outletId);
    }

    /// <summary>
    /// Powers on a specific outlet on the KMTronic relay device by sending a command to either open or close the relay, depending on the configured mode.
    /// </summary>
    /// <param name="outletId">The ID of the outlet to power on.</param>
    public void OpenRelay(int outletId)
    {
        //Activa el relé por lo que corta la corriente del dispositivo conectado
        Send("FF0" + outletId + "01");
    }

    /// <summary>
    /// Powers on a specific outlet on the KMTronic relay device by sending a command to either open or close the relay, depending on the configured mode.
    /// </summary>
    /// <param name="outletId">The ID of the outlet to power on.</param>
    public override void PowerOnRelay(int outletId)
    {
        if (Relay.Mode == RelayEnum.Mode.OpenedOffClosedOn) CloseRelay(outletId);
        else OpenRelay(outletId);
    }

    /// <summary>
    /// Closes a specific outlet on the KMTronic relay device by sending a command to either open or close the relay, depending on the configured mode. 
    /// This method is used to power on the outlet by allowing the current to flow through it.
    /// </summary>
    /// <param name="outletId">The ID of the outlet to close.</param>
    public void CloseRelay(int outletId)
    {
        //Desactiva el relé por lo que deja pasar la corriente del dispositivo conectado
        Send("FF0" + outletId + "00");
    }


    /// <summary>
    /// Powers off all outlets on the KMTronic relay device by sending a command to either open or close all relays, depending on the configured mode.
    /// </summary>
    public override void PowerOffAll()
    {
        //Activa el relé por lo que corta la corriente de todos los dispositivo conectados
        Send("FFE0FF");
    }

    /// <summary>
    /// Powers on all outlets on the KMTronic relay device by sending a command to either open or close all relays, depending on the configured mode.
    /// </summary>
    public override void PowerOnAll()
    {
        //Desactiva el relé por lo que deja pasar la corriente a todos dispositivo conectados
        Send("FFE000");
    }

    /// <summary>
    /// Checks the status of the KMTronic relay device by calling the GetStatusRelay method and updating the DeviceStatus property accordingly.
    /// </summary>
    public override void CheckStatus()
    {
        DeviceStatus = GetStatusRelay();
    }

    /// <summary>
    /// Reboots a specific outlet on the KMTronic relay device by first powering it off, waiting for a specified delay, and then powering it back on. This method is used to reset the connected device by briefly cutting off its power supply.
    /// </summary>
    /// <param name="outletId">The ID of the outlet to reboot.</param>
    public override void RebootOutlet(int outletId)
    {
        //Reinicia el relé del dispositivo conectado            
        PowerOnOffRelay(outletId, 2000);
    }

    /// <summary>
    /// Reboots all outlets on the KMTronic relay device by first powering them off, waiting for a specified delay, and then powering them back on. This method is used to reset all connected devices by briefly cutting off their power supply.
    /// </summary>
    public override void Reboot()
    {
        PowerOffAll();
        if (Relay.RebootDelay is > 0) Thread.Sleep(Relay.RebootDelay!.Value);
        PowerOnAll();
    }

    /// <summary>
    /// Asynchronously retrieves the status of the KMTronic relay device. This method is not implemented in this class, and it throws a NotImplementedException when called.
    /// </summary>
    /// <returns>A task representing the asynchronous operation.</returns>
    /// <exception cref="NotImplementedException"></exception>
    public override Task<bool> GetStatusRelayAsync()
    {
        throw new NotImplementedException();
    }

    /// <summary>
    /// Asynchronously retrieves the status of a specific outlet on the KMTronic relay device. This method is not implemented in this class, and it throws a NotImplementedException when called.
    /// </summary>
    protected virtual void Connect()
    {
        if (_udpClient == null)
            try
            {
                if (!string.IsNullOrEmpty(Relay.Ip)) _udpClient = new UdpClient(Relay.Ip, Relay.Port);
                //var ipaddress = IPAddress.Parse(RelayConfig.Ip);
                //sourceipendpoint = new IPEndPoint(ipaddress, port);    //source port IPEndpoint
            }
            catch (Exception e)
            {
                Log.Debug("UdpClient not connect {Host}: {Port} Ex: {Ex}", Relay.Ip, Relay.Port, e.Message);

                try
                {
                    if (_udpClient != null)
                    {
                        _udpClient.Close();
                        _udpClient = null;
                    }
                }
                catch
                {
                    // ignored
                }
            }
        else
            try
            {
                _udpClient.Close();
                _udpClient = null;
            }
            catch
            {
                // ignored
            }
    }

    /// <summary>
    /// Sends a command to the KMTronic relay device and waits for a response. 
    /// It first checks if the UDP client is initialized and connected, and if not, it attempts to connect to the device. 
    /// If the connection is successful, it sends the specified command to the device and waits for a response.
    /// </summary>
    /// <param name="datagrama">The command to send to the KMTronic relay device.</param>
    /// <returns>The response from the KMTronic relay device.</returns>
    protected virtual string Send(string datagrama)
    {
        if (_udpClient == null) Connect();

        if (_udpClient == null) return string.Empty;

        var command = Encoding.ASCII.GetBytes(datagrama.ToUpper());
        _udpClient.Send(command, command.Length);

        var returnData = string.Empty;
        try
        {
            var t = Task.Run(() =>
            {
                var taskReceiveBytes = _udpClient.ReceiveAsync();
                returnData = Encoding.ASCII.GetString(taskReceiveBytes.Result.Buffer);
            });
            var ts = TimeSpan.FromSeconds(5);
            if (!t.Wait(ts))
            {
                if (!t.IsCompletedSuccessfully) returnData = string.Empty;
                if (t.Exception != null) throw t.Exception;
            }

            if (t.Status == TaskStatus.RanToCompletion) t.Dispose();
        }
        catch (Exception ex)
        {
            Log.Debug("UdpClient not connect {Host}: {Port} Ex: {Ex}", Relay.Ip, Relay.Port, ex.Message);
            returnData = string.Empty;
        }

        if (returnData == string.Empty)
            Log.Debug("UdpClient not connect {Host}: {Port}", Relay.Ip, Relay.Port);

        return returnData;
    }
}