using System.Net;
using System.Xml;
using adas_core.Domain.Enums;
using adas_core.Domain.Models.MongoModels;
using adas_core.module.Relays.Models;
using adas_core.module.Relays.Utils;
using Serilog;

namespace adas_core.module.Relays.Devices;

/// <summary>
/// Class representing a KMTronic V2 relay device. This class inherits from the RelayDevice class and provides specific implementations for checking the device status, getting the status of individual relays, and controlling the power state of the relays for a KMTronic V2 relay device.
/// </summary>
public class KmTronicV2Relay : RelayDevice
{
    /// <summary>
    /// Constructor for the KmTronicV2Relay class. It initializes the base RelayDevice class with the provided Relay and RelaySettings objects, and then checks the device status to ensure it is functioning correctly.
    /// </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 KmTronicV2Relay(Relay relay, RelaySettings relaySettings) : base(relay, relaySettings)
    {
        CheckDevice();
    }

    /// <summary>
    /// Checks the status of the KMTronic V2 relay device by sending a request to retrieve the status of the relays. 
    /// It updates the internal status of each relay based on the response received. 
    /// The method returns true if the device is responsive and provides a valid status, and false otherwise.
    /// </summary>
    /// <returns>True if the device is responsive and provides a valid status, false otherwise.</returns>
    public sealed override bool CheckDevice()
    {
        CheckStatus();
        var result = GetStatusRelay(1);
        return result != RelayEnum.Status.Unknown;
    }

    /// <summary>
    /// Checks the status of the KMTronic V2 relay device by sending a request to retrieve the status of the relays.
    /// </summary> 
    /// <remarks>
    /// The method sends an HTTP GET request to the "status.xml" endpoint of the KMTronic V2 relay device and parses the XML response to update the internal status of each relay.
    /// </remarks>
    public override void CheckStatus()
    {
        try
        {
            var httpResponse = SendRequest("status.xml", HttpMethod.Get);

            if (httpResponse.StatusCode.Equals(HttpStatusCode.OK))
            {
                var responseContent = httpResponse.Content;
                var responseString = responseContent.ReadAsStringAsync().Result;
                if (!string.IsNullOrEmpty(responseString))
                {
                    var response = new XmlDocument();
                    response.LoadXml(responseString);
                    foreach (XmlNode node in response.SelectNodes("//response")!)
                    foreach (XmlNode childNode in node.ChildNodes)
                    {
                        if (!childNode.Name.StartsWith("relay") || string.IsNullOrEmpty(childNode.InnerText)) continue;
                        var id = int.Parse(childNode.Name[5..]);
                        if (id <= 0 || id > Relay.Total) continue;
                        var value = int.Parse(childNode.InnerText);
                        var relayStatus = value.Equals(0) ? RelayEnum.Status.Off :
                            value.Equals(1) ? RelayEnum.Status.On : RelayEnum.Status.Unknown;
                        Tuple.Create(Relay.Ip, Relay.Port, id);
                        SetStatusRelay(id, relayStatus);
                    }
                }

                Log.Debug(responseContent.ReadAsStringAsync().Result);
            }
            else
            {
                Log.Debug("Status code = " + httpResponse.StatusCode);
            }
        }
        catch (Exception ex)
        {
            Log.Error(ex.ToString());
            Enumerable.Range(1, Relay.Total).ToList().ForEach(outletId =>
            {
                SetStatusRelay(outletId, RelayEnum.Status.Unknown);
            });
        }
    }

    /// <summary>
    /// Gets the status of a specific relay outlet by sending a request to the KMTronic V2 relay device. The method returns the status of the specified relay outlet, which can be On, Off, or Unknown.
    /// </summary>
    /// <returns>True if the specified relay outlet is On, false otherwise.</returns>
    public override bool GetStatusRelay()
    {
        return GetStatusRelay(1) == RelayEnum.Status.On;
    }

    /// <summary>
    /// Gets the status of a specific relay outlet by sending a request to the KMTronic V2 relay device. The method returns the status of the specified relay outlet, which can be On, Off, or Unknown.
    /// </summary>
    /// <returns>True if the specified relay outlet is On, false otherwise.</returns>
    public override Task<bool> GetStatusRelayAsync()
    {
        var result = GetStatusRelay();
        return Task.FromResult(result);
    }

    /// <summary>
    /// Gets the status of a specific relay outlet by retrieving the status from the internal status dictionary. The method returns the status of the specified relay outlet, which can be On, Off, or Unknown.    
    /// </summary>
    /// <param name="outletId">The ID of the relay outlet.</param>
    public override void PowerOnRelay(int outletId)
    {
        PowerRelay(outletId, RelayEnum.Status.On);
    }

    /// <summary>
    /// Gets the status of a specific relay outlet by retrieving the status from the internal status dictionary. The method returns the status of the specified relay outlet, which can be On, Off, or Unknown.
    /// </summary>
    /// <param name="outletId">The ID of the relay outlet.</param>
    public override void PowerOffRelay(int outletId)
    {
        PowerRelay(outletId, RelayEnum.Status.Off);
    }

    /// <summary>
    /// Constructs a URI for sending requests to the KMTronic V2 relay device based on the specified path and query parameters. 
    /// The method builds the URI using the IP address, port, and path of the relay device, and includes any query parameters if provided.
    /// </summary>
    /// <param name="path">The path of the relay device.</param>
    /// <param name="query">The query parameters for the request.</param>
    /// <returns>The constructed URI.</returns>
    private Uri GetUri(string path, string? query = null)
    {
        var uriBuilder = new UriBuilder
            { Scheme = "http", Host = Relay.Ip, Port = Relay.Port, Path = path };
        if (!string.IsNullOrEmpty(query)) uriBuilder.Query = query;
        return uriBuilder.Uri;
    }

    /// <summary>
    /// Constructs an HttpRequestMessage for sending requests to the KMTronic V2 relay device based on the specified path, HTTP method, and query parameters.
    /// </summary>
    /// <param name="path">The path of the relay device.</param>
    /// <param name="method">The HTTP method for the request.</param>
    /// <param name="query">The query parameters for the request.</param>
    /// <returns>The constructed HttpRequestMessage.</returns>
    private HttpRequestMessage GetRequest(string path, HttpMethod method, string? query = null)
    {
        var uri = GetUri(path, query);
        var request = new HttpRequestMessage
        {
            RequestUri = uri,
            Method = method
        };

        if (!string.IsNullOrEmpty(Relay.Username) && !string.IsNullOrEmpty(Relay.Password))
            HttpUtils.AddBasicAuth(Relay.Username, Relay.Password, request.Headers);
        return request;
    }

    /// <summary>
    /// Sends an HTTP request to the KMTronic V2 relay device using the specified path, HTTP method, and query parameters. 
    /// The method constructs the request using the GetRequest method and sends it using an HttpClient. 
    /// It handles any exceptions that may occur during the request and returns the HttpResponseMessage received from the relay device.
    /// </summary>
    /// <param name="path">The path of the relay device.</param>
    /// <param name="method">The HTTP method for the request.</param>
    /// <param name="query">The query parameters for the request.</param>
    /// <returns>The HttpResponseMessage received from the relay device.</returns>
    private HttpResponseMessage SendRequest(string path, HttpMethod method, string? query = null)
    {
        using var client = new HttpClient();
        client.Timeout = TimeSpan.FromSeconds(15);
        try
        {
            return client.Send(GetRequest(path, method, query));
        }
        catch (Exception e)
        {
            Log.Warning("Error sending request to kmtronic ip {IP} port {Port}: {Message}", Relay.Ip,
                Relay.Port, e.Message);
            return new HttpResponseMessage(HttpStatusCode.Gone);
        }
    }


    /// <summary>
    /// Controls the power state of a specific relay outlet by sending a request to the KMTronic V2 relay device. 
    /// The method checks the current status of the specified relay outlet and sends a request to change its state to the desired state (On or Off). 
    /// If the checkStatus parameter is set to true, it will also check the status of the device after sending the request to ensure that the state change was successful.
    /// </summary>
    /// <param name="outletId">The ID of the relay outlet to control.</param>
    /// <param name="toState">The desired state for the relay outlet (On or Off).</param>
    /// <param name="checkStatus">Whether to check the status of the device after sending the request.</param>
    private void PowerRelay(int outletId, RelayEnum.Status toState, bool checkStatus = true)
    {
        if (Relay.Mode == RelayEnum.Mode.OpenedOffClosedOn)
            toState = toState == RelayEnum.Status.Off ? RelayEnum.Status.On : RelayEnum.Status.Off;

        var status = GetStatusRelay(outletId);

        if (status == toState) return;


        //No entiendo esto así, siempre enciende/apaga el mismo uriBuilder.Query = $"relay={outlet.outlet}";
        //me quedo con el nombre y solo con el número del rele que es la llamada que el hace realmente desde la web
        //No uso una regex porque es mucho más lento            

        try
        {
            SendRequest("relays.cgi", HttpMethod.Post, $"relay={outletId}");
        }
        catch (Exception ex)
        {
            Log.Debug(ex.Message);
        }
        finally
        {
            if (checkStatus) CheckStatus();
        }
    }

    /// <summary>
    /// Controls the power state of all relay outlets by sending requests to the KMTronic V2 relay device. 
    /// The method iterates through all relay outlets and sends requests to change their state to the desired state (On or Off). 
    /// After sending the requests, it checks the status of the device to ensure that the state changes were successful.
    /// </summary>
    public override void PowerOnAll()
    {
        PowerAll(RelayEnum.Status.On);
    }

    /// <summary>
    /// Controls the power state of all relay outlets by sending requests to the KMTronic V2 relay device.
    /// </summary>
    public override void PowerOffAll()
    {
        PowerAll(RelayEnum.Status.Off);
    }

    /// <summary>
    /// Controls the power state of all relay outlets by sending requests to the KMTronic V2 relay device.
    /// </summary>
    /// <param name="toState">The desired state for all relay outlets (On or Off).</param>
    private void PowerAll(RelayEnum.Status toState)
    {
        Enumerable.Range(1, Relay.Total).ToList().ForEach(outletId => { PowerRelay(outletId, toState, false); });
        CheckStatus();
    }

    /// <summary>
    /// Reboots the KMTronic V2 relay device by first powering off all relay outlets, then waiting for a specified delay (if configured), and finally powering on all relay outlets again.
    /// </summary>
    public override void Reboot()
    {
        PowerOffAll();
        if (Relay.RebootDelay is > 0) Thread.Sleep(Relay.RebootDelay!.Value);
        PowerOnAll();
    }

    /// <summary>
    /// Reboots a specific relay outlet by first powering it off, then waiting for a specified delay (30 seconds in this case), and finally powering it on again.
    /// </summary>
    /// <param name="outletId">The ID of the relay outlet to reboot.</param>
    public override void RebootOutlet(int outletId)
    {
        PowerOffRelay(outletId);
        Thread.Sleep(30000);
        PowerOnRelay(outletId);
    }
}