using adas_core.Domain.Enums;
using adas_core.Domain.Models;
using adas_core.Domain.Utils;
using HtmlAgilityPack;
using Serilog;

namespace adas_core.module.LightBeacons.Devices;

public class Turktbens2LightBeacon : LightBeacon
{
    private readonly HttpClient _client;
    private readonly int _code = 75;
    private readonly bool _emulate;
    private readonly ILogger _logger;
    private readonly string _password = "password";
    private readonly int _port = 1;

    private readonly Dictionary<int, int[]> _portMappings = new()
    {
        { 1, [0, 1] },
        { 2, [2, 3] },
        { 3, [4, 5] },
        { 4, [6, 7] }
    };

    private readonly Dictionary<int, string[]> _ports = new()
    {
        { 1, ["|1|1|1|2", "|12|0|1|0", "|1|0|1|2"] },
        { 2, ["|1|3|1|2", "|12|1|1|0", "|1|2|1|2"] },
        { 3, ["|1|5|1|2", "|12|2|1|0", "|1|4|1|2"] },
        { 4, ["|1|7|1|2", "|12|3|1|0", "|1|6|1|2"] }
    };

    private readonly int _timeout = 30;

    private readonly string _url;

    public Turktbens2LightBeacon(EquatableDictionary<string, object> options) : base(options)
    {
        _logger = Log.ForContext<Turktbens2LightBeacon>();
        if (options == null) throw new ArgumentException("TURKTBENS2Beacon has no options");
        if (!options.TryGetValue("url", out var url))
            throw new ArgumentException("TURKTBENS2Beacon has no valid url option");
        if (url == null) throw new ArgumentException("TURKTBENS2Beacon has no valid url option");
        _url = url.ToString()!;
        if (options.TryGetValue("password", out var password)) _password = password.ToString() ?? "password";
        if (options.TryGetValue("port", out var port)) _port = Convert.ToInt32(port);
        if (options.TryGetValue("timeout", out var timeout)) _timeout = Convert.ToInt32(timeout);
        if (options.TryGetValue("code", out var code)) _code = Convert.ToInt32(code);
        if (options.TryGetValue("emulate", out var emulate)) _emulate = Convert.ToBoolean(emulate);
        _client = new HttpClient
        {
            Timeout = TimeSpan.FromSeconds(_timeout)
        };
    }


    /// <summary>
    ///     GetByCodeSysAndCode beacon color downloading the html of the web. and using AgilityPackHtml to access.
    ///     The web is bugged and sometimes you are logged or not. The information is in two urls, the color is formed by 3
    ///     bits, Vaux html changes if logged/not logged
    ///     returns OFF when some error appears or try to parse the bits to BalizaColors enum
    /// </summary>
    /// <returns></returns>
    public override async Task<LightBeaconColor> GetBeaconColor()
    {
        if (_emulate)
            return LightBeaconColor.Off;

        var httpClient = new HttpClient();

        try
        {
            //we dont need to login to check status
            var vauxValue = 0;
            var output1Value = 0;
            var output2Value = 0;
            var outputUri = new Uri($"{_url}/IO01_03.html");
            var vauxUri = new Uri($"{_url}/IO12_03.html");

            var outputs = await httpClient.GetAsync(outputUri);
            var vauxs = await httpClient.GetAsync(vauxUri);
            if (!outputs.IsSuccessStatusCode || !vauxs.IsSuccessStatusCode)
                return LightBeaconColor.Off;

            var outputsHtml = new HtmlDocument();
            outputsHtml.LoadHtml(await outputs.Content.ReadAsStringAsync());

            var vauxHtml = new HtmlDocument();
            vauxHtml.LoadHtml(await vauxs.Content.ReadAsStringAsync());

            //GetByCodeSysAndCode Vaux info from vauxUri can be on off and we translate it to 0,1
            var trElements = vauxHtml.DocumentNode.SelectNodes("//tr");
            if (trElements == null || trElements.Count < _port) 
                return LightBeaconColor.Off;

            var portNode = trElements[_port - 1];
            var tdNodes = portNode.SelectNodes(".//td");
            var vauxResult = tdNodes[1];
            switch (vauxResult.InnerText)
            {
                case "on":
                    vauxValue = 1;
                    break;
                //sometimes it display a select option when logged and other times its a label. this case is for option, take selected option
                case "offon":
                {
                    var selected = vauxResult.SelectSingleNode("select").SelectSingleNode("option[@selected]");
                    if (selected.InnerText.Equals("on")) vauxValue = 1;

                    break;
                }
            }

            //GetByCodeSysAndCode outputs info
            var checkboxElements = outputsHtml.DocumentNode.SelectNodes("//input[@type='checkbox']");
            if (checkboxElements == null) 
                return LightBeaconColor.Off;

            var portsParsed = _portMappings.TryGetValue(_port, out var mappedValues);
            if (!portsParsed) return LightBeaconColor.Off;
            var output1 = checkboxElements[mappedValues![0]];
            var output2 = checkboxElements[mappedValues[1]];

            if (output1.Attributes["checked"] != null) output1Value = 1;
            if (output2.Attributes["checked"] != null) output2Value = 1;

            var finalValue = $"{output2Value}{vauxValue}{output1Value}";
            var parsed = Enum.TryParse(finalValue, out LightBeaconColor parsedColor);
            return parsed ? parsedColor : LightBeaconColor.Off;
        }
        catch (Exception e)
        {
            _logger.Error(
                "Exception trying to get beacon color for beacon: {url} port: {port}  ex: {eMessage}, trace: {eStackTrace}",
                _url, _port, e.Message, e.StackTrace);
            return LightBeaconColor.Off;
        }
        finally
        {
            httpClient.Dispose();
        }
    }

    public override async Task BlueCode()
    {
        try
        {
            await Send(ColorsCodes.Blue);
        }
        catch (Exception e)
        {
            _logger.Error("An error occurred while sending the Blue code: {Message}", e.Message);
            throw;
        }
    }

    public override async Task RedCode()
    {
        try
        {
            await Send(ColorsCodes.Red);
        }
        catch (Exception e)
        {
            _logger.Error("An error occurred while sending the Red code: {Message}", e.Message);
            throw;
        }
    }

    public override async Task YellowCode()
    {
        try
        {
            await Send(ColorsCodes.Yellow);
        }
        catch (Exception e)
        {
            _logger.Error("An error occurred while sending the Yellow code: {Message}", e.Message);
            throw;
        }
    }

    public override async Task PowerOffLed()
    {
        try
        {
            await Send(ColorsCodes.Off);
        }
        catch (Exception e)
        {
            _logger.Error("An error occurred while sending the power off code: {Message}", e.Message);
            throw;
        }
    }

    public override Task GenerateColorAlert(PatientObservation obs)
    {
        throw new NotImplementedException();
    }


    private async Task Send(ColorsCodes colorCodes)
    {
        var clr = ((int)colorCodes).ToString().PadLeft(3, '0');
        var passwordEncoded = Uri.EscapeDataString(_password);
        var body = new Dictionary<string, string>
        {
            { "access_password", passwordEncoded },
            { _code + _ports[_port][0], clr[0].ToString() },
            { _code + _ports[_port][1], clr[1].ToString() },
            { _code + _ports[_port][2], clr[2].ToString() }
        };

        if (!_emulate)
            try
            {
                await SendMessage(body);
            }
            catch (Exception e)
            {
                _logger.Error("Failed to send color codes: {errorMessage}", e.Message);
                throw;
            }
    }


    protected async Task SendMessage(Dictionary<string, string> body)
    {
        if (string.IsNullOrEmpty(_url)) throw new InvalidOperationException("URL is not set.");

        var retryCount = 0;
        const int maxRetries = 3; // Número máximo de intentos
        var delayMilliseconds = 500; // Tiempo de espera inicial entre reintentos

        while (retryCount < maxRetries)
            try
            {
                var uri = new Uri(_url);
                var content = new FormUrlEncodedContent(body);
                var b = await content.ReadAsStringAsync();
                _logger.Debug("Sending to {url} body {b}", _url, b);
                var response = await _client.PostAsync(uri, content);

                if (!response.IsSuccessStatusCode)
                    throw new Exception(
                        $"HTTP error: {response.StatusCode} while sending message to {_url} with body {b}");
                break;
            }
            catch (Exception e)
            {
                retryCount++;
                _logger.Warning(
                    "Error sending to url: {url}, message: {eMessage}, stack trace: {eStackTrace}, attempt: {retryCount}",
                    _url, e.Message, e.StackTrace, retryCount);

                if (retryCount >= maxRetries)
                {
                    _logger.Error("Max retry attempts reached. Failing operation.");
                    throw;
                }

                await Task.Delay(delayMilliseconds);
                delayMilliseconds *= 2; // Incrementa el tiempo de espera para cada reintento
            }
    }

    private enum ColorsCodes
    {
        Off = 000,
        Red = 001,
        Yellow = 011,
        Green = 010,
        Cyan = 110,
        Blue = 100,
        Magenta = 101,
        White = 111
    }
}