using System.Collections.Concurrent;
using System.Text;
using adas_core.Application.Repositories.Interfaces;
using adas_core.Application.Services.Interfaces;
using adas_core.Domain.Enums;
using adas_core.Domain.Models;
using adas_core.Domain.Models.AppSettings;
using adas_core.Domain.Models.Filter;
using adas_core.Domain.Models.MongoModels;
using adas_core.Domain.Models.Responses;
using adas_core.Domain.Utils;
using adas_core.module.LightBeacons.Devices;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using MongoDB.Bson;
using MongoDB.Driver;
using LightBeaconDevice = adas_core.module.LightBeacons.Devices.LightBeacon;
using LightBeacon = adas_core.Domain.Models.MongoModels.LightBeacon;

namespace adas_core.module.LightBeacons.Services;

/// <summary>
/// Service responsible for managing light beacons, including sending color alerts, powering off LEDs, and broadcasting beacon status to subscribers. 
/// It interacts with the point of care service to determine which beacons are associated with specific locations and maintains an in-memory cache of the current color state of each location's beacon. 
/// The service also handles CRUD operations for light beacon configurations stored in a MongoDB repository.
/// </summary>
public class LightBeaconService : ILightBeaconService
{
    private readonly IClientMessageService _clientMessageService;

    private readonly ConcurrentDictionary<ObjectId, LightBeaconColor> _locationsWithColor = new();

    private readonly ILogger<LightBeaconService> _logger;
    private readonly IPointOfCareService _pointOfCareService;
    private readonly ILightBeaconRepository _lightBeaconRepository;
    private readonly ISubscribersService _subscribersService;

    /// <summary>
    /// Initializes a new instance of the LightBeaconService class with the specified dependencies and configuration settings.
    /// </summary>
    /// <param name="apiSettings">The API settings containing configuration values for the light beacon service.</param>
    /// <param name="logger">The logger instance for logging information and errors.</param>
    /// <param name="clientMessageService">The client message service for sending messages to clients.</param>
    /// <param name="subscribersService">The subscribers service for managing subscribers.</param>
    /// <param name="pointOfCareService">The point of care service for retrieving point of care information.</param>
    /// <param name="lightBeaconRepository">The light beacon repository for managing light beacon data.</param>
    public LightBeaconService(
        IOptions<ApiSettings> apiSettings,
        ILogger<LightBeaconService> logger,
        IClientMessageService clientMessageService,
        ISubscribersService subscribersService,
        IPointOfCareService pointOfCareService,
        ILightBeaconRepository lightBeaconRepository)
    {
        _logger = logger;
        _clientMessageService = clientMessageService;
        _subscribersService = subscribersService;
        _pointOfCareService = pointOfCareService;
        _lightBeaconRepository = lightBeaconRepository;

        try
        {
            Url = apiSettings.Value.BalizaUrl;
        }
        catch (Exception ex)
        {
            _logger.LogError("Error BalizaService apiSettings values: {exMessage} ", ex);
        }

        //_ = SetLocationsWithColor();
    }

    /// <summary>
    /// Gets the URL for the light beacon API from the configuration settings. This URL is used to send commands to the light beacon devices.
    /// </summary>
    private string? Url { get; }

    /// <summary>
    /// Powers off the LED of the light beacon associated with the specified point of care ID. It retrieves the point of care information, 
    /// checks if there are any associated beacons, and sends a command to turn off the LED for each associated beacon. 
    /// The method also updates the in-memory cache to reflect that the beacon is now off for the specified location.
    /// </summary>
    /// <param name="pocId">The ID of the point of care for which to power off the LED.</param>
    /// <returns>A task representing the asynchronous operation.</returns>
    public async Task PowerOffLed(ObjectId pocId)
    {
        var poc = await _pointOfCareService.FindById(pocId);
        if (poc == null)
        {
            _logger.LogError("Power off led did not find poc by id: {PocId}", pocId);
            return;
        }

        _ = PowerOffLed(poc);
    }

    /// <summary>
    /// Inserts a new light beacon configuration into the repository. Before inserting, it checks if a beacon with the same name already exists to prevent duplicates.
    /// </summary>
    /// <param name="beacon">The light beacon configuration to insert.</param>
    /// <returns>The inserted light beacon configuration, or null if the insertion failed.</returns>
    /// <exception cref="Exception">Thrown if a light beacon with the same name already exists.</exception>
    public async Task<LightBeacon?> InsertOne(LightBeacon beacon)
    {
        var beaconFound = await _lightBeaconRepository.GetByName(beacon.Name);
        if(beaconFound != null) throw new Exception($"Light beacon with name {beacon.Name} already exists");
        return await _lightBeaconRepository.InsertOneAsyncAndReturn(beacon);
    }

    /// <summary>
    /// Retrieves a paginated list of light beacons from the repository based on the specified pagination filter. 
    /// The method also checks which beacons are currently in use by querying the point of care service and updates the "InUse" property of each beacon accordingly before returning the paginated response.
    /// </summary>
    /// <param name="filter">The pagination filter to apply when retrieving the light beacons.</param>
    /// <returns>A paginated response containing the light beacons that match the specified filter.</returns>
    public async Task<PaginationResponse<LightBeacon>> GetPaginatedBeacons(PaginationFilter filter)
    {
        var usedRelayIds = await _pointOfCareService.FindAllIdBeaconsInUse();

        var fluentQuery = _lightBeaconRepository.GetPaginatedRelays(filter);

        if (filter.FilteredRequest?.InUse != null)
        {
            bool filterInUse = filter.FilteredRequest.InUse.Value;
            var filterBuilder = Builders<LightBeacon>.Filter;
        
            var idFilter = filterInUse 
                ? filterBuilder.In(c => c.Id, usedRelayIds)
                : filterBuilder.Not(filterBuilder.In(c => c.Id, usedRelayIds));

            fluentQuery.Filter = filterBuilder.And(fluentQuery.Filter, idFilter);
        }

        var count = await fluentQuery.CountDocumentsAsync();
        var data = await fluentQuery
            .Skip((filter.PageNumber - 1) * filter.PageSize)
            .Limit(filter.PageSize)
            .ToListAsync();

        if(data == null) return new PaginationResponse<LightBeacon>([], filter.PageNumber, filter.PageSize, count);

        foreach (var camera in data)
        {
            if (camera == null) continue;

            bool isInUse = usedRelayIds.Contains(camera.Id);
        
            // Asignación mediante reflexión para el private set
            camera.GetType().GetProperty(nameof(Relay.InUse))
                ?.SetValue(camera, isInUse);
        }

        return new PaginationResponse<LightBeacon>(data, filter.PageNumber, filter.PageSize, count);
    }

    /// <summary>
    /// Searches for light beacons in the repository that match the specified text in their name. The method returns a list of light beacons whose names contain the provided search text, allowing for partial matches.
    /// </summary>
    /// <param name="textToSearch">The text to search for in the names of the light beacons.</param>
    /// <returns>A list of light beacons whose names contain the specified search text.</returns>
    public async Task<List<LightBeacon>> GetSearchByName(string textToSearch)
    {
        return await _lightBeaconRepository.GetSearchByName(textToSearch);
    }

    /// <summary>
    /// Updates an existing light beacon configuration in the repository. 
    /// The method takes a light beacon object with updated properties, updates the corresponding record in the repository based on the beacon's ID, and then retrieves and returns the updated light beacon configuration to confirm the changes.
    /// </summary>
    /// <param name="beacon">The light beacon object with updated properties.</param>
    /// <returns>The updated light beacon configuration, or null if the update failed.</returns>
    public async Task<LightBeacon?> UpdateOne(LightBeacon beacon)
    {
        await _lightBeaconRepository.UpdateOneAsync(beacon.Id, beacon);
        return await _lightBeaconRepository.GetById(beacon.Id);
    }

    /// <summary>
    /// Powers off the LED of the light beacon associated with the specified point of care. 
    /// The method checks if the beacon is already off to avoid unnecessary commands, retrieves the beacon configuration for the point of care, and sends a command to turn off the LED for each associated beacon.
    /// </summary>
    /// <param name="poc">The point of care for which to power off the LED.</param>
    /// <returns>A task representing the asynchronous operation.</returns>
    public async Task PowerOffLed(PointOfCare poc)
    {
        if (_locationsWithColor.TryGetValue(poc.Id, out var lightBeaconColor) &&
            lightBeaconColor == LightBeaconColor.Off)
            return;

        var beaconConfig = poc.Configuration?.BeaconIdList;
        if (beaconConfig == null || !beaconConfig.Any()) return;
        foreach (var beacon in beaconConfig.Select(GetBeacon))
            if (beacon != null)
                await SendColor(poc.Id, LightBeaconColor.Off);

        lock (_locationsWithColor)
        {
            if (_locationsWithColor.ContainsKey(poc.Id))
                _locationsWithColor[poc.Id] = LightBeaconColor.Off;

            else
                _locationsWithColor.TryAdd(poc.Id, LightBeaconColor.Off);
        }

        _logger.LogDebug("Locations with color cached: {dct}", DictionaryToString(_locationsWithColor));
    }

    /// <summary>
    /// Generates a color alert for a patient observation by determining the appropriate color based on the observation's status and configured colors for warnings and alerts.
    /// </summary>
    /// <param name="obs">The patient observation for which to generate the color alert.</param>
    public void GenerateColorAlert(PatientObservation obs)
    {
        try
        {
            LightBeaconColor colorEnum;
            var colorNumbers = new char[3];

            switch (obs.Status)
            {
                case StatusEnum.Type.Warning:
                    if (obs.WarnColor == null) return;
                    colorEnum = (LightBeaconColor)Enum.Parse(typeof(LightBeaconColor),
                        obs.WarnColor);
                    var colorValue = (int)colorEnum;
                    var valuesChart = colorValue.ToString().ToCharArray();
                    if (valuesChart.Length is < 3 and < 2)
                    {
                        var c = valuesChart[0];
                        colorNumbers[0] = '0';
                        colorNumbers[1] = '0';
                        colorNumbers[2] = c;
                    }
                    else if (valuesChart.Length < 3)
                    {
                        var c = valuesChart[0];
                        var c2 = valuesChart[1];
                        colorNumbers[0] = '0';
                        colorNumbers[1] = c;
                        colorNumbers[2] = c2;
                    }

                    break;

                case StatusEnum.Type.Alert:
                    if (obs.AlertColor == null) return;
                    colorEnum = (LightBeaconColor)Enum.Parse(typeof(LightBeaconColor),
                        obs.AlertColor);
                    var value = (int)colorEnum;
                    var vchart = value.ToString().ToCharArray();
                    if (vchart.Length is < 3 and < 2)
                    {
                        var c = vchart[0];
                        colorNumbers[0] = '0';
                        colorNumbers[1] = '0';
                        colorNumbers[2] = c;
                    }
                    else if (vchart.Length < 3)
                    {
                        var c = vchart[0];
                        var c2 = vchart[1];
                        colorNumbers[0] = '0';
                        colorNumbers[1] = c;
                        colorNumbers[2] = c2;
                    }

                    break;


                default:

                    colorNumbers[0] = '0';
                    colorNumbers[1] = '0';
                    colorNumbers[2] = '0';

                    break;
            }

            var passwordEncoded = Uri.EscapeDataString("password");
            var body =
                $"access_password={passwordEncoded}&58|12|1|1|0={Uri.EscapeDataString(colorNumbers[0].ToString())}&58|1|3|1|2={Uri.EscapeDataString(colorNumbers[1].ToString())}&58|1|2|1|2={Uri.EscapeDataString(colorNumbers[2].ToString())}";
            _ = SendMessage(body);
        }
        catch (Exception ex)
        {
            _logger.LogError("Error GenerateColorAlert {obs}: {exMessage}", obs, ex);
        }
    }

    /// <summary>
    /// Sends a color command to the light beacon associated with the specified point of care ID. 
    /// The method retrieves the point of care information, checks if there are any associated beacons, and sends a command to set the specified color for each associated beacon.
    /// </summary>
    /// <param name="pocId">The ID of the point of care for which to send the color command.</param>
    /// <param name="color">The color to set on the light beacon.</param>
    /// <returns>A task representing the asynchronous operation.</returns>
    public async Task SendColor(ObjectId pocId, LightBeaconColor color)
    {
        var poc = await _pointOfCareService.FindById(pocId);
        if (poc == null) return;
        _ = SendColor(poc, color);
    }

    /// <summary>
    /// Sends a color command to the light beacon associated with the specified point of care. 
    /// The method checks if the beacon is already set to the specified color to avoid unnecessary commands, retrieves the beacon configuration for the point of care, and sends a command to set the specified color for each associated beacon.
    /// </summary>
    /// <param name="poc">The point of care for which to send the color command.</param>
    /// <param name="color">The color to set on the light beacon.</param>
    /// <returns>A task representing the asynchronous operation.</returns>
    public async Task SendColor(PointOfCare poc, LightBeaconColor color)
    {
        try
        {
            if (_locationsWithColor.TryGetValue(poc.Id, out var lightBeaconColor) && lightBeaconColor == color)
                return;

            var beaconConfig = poc.Configuration?.BeaconIdList;
            if (beaconConfig == null || !beaconConfig.Any()) return;
            foreach (var beaconId in beaconConfig)
            {
                var beacon = await GetBeacon(beaconId);
                switch (color)
                {
                    case LightBeaconColor.Red:
                        beacon?.RedCode();
                        break;
                    case LightBeaconColor.Blue:
                        beacon?.BlueCode();
                        break;
                    case LightBeaconColor.Yellow:
                        beacon?.YellowCode();
                        break;
                    case LightBeaconColor.Off:
                        beacon?.PowerOffLed();
                        break;
                }

                await SendBeaconBroadcast(poc, color);

                lock (_locationsWithColor)
                {
                    if (_locationsWithColor.ContainsKey(poc.Id))
                        _locationsWithColor[poc.Id] = color;
                    else
                        _locationsWithColor.TryAdd(poc.Id, color);
                }
            }

            _logger.LogDebug("Locations with color cached: {dct}", DictionaryToString(_locationsWithColor));
        }
        catch (Exception e)
        {
            _logger.LogError("An error occurred on sending color {color} while sending the Beacon code: {Message}",
                color.ToString(), e.Message);
        }
    }

    /// <summary>
    /// Retrieves the current color state of the light beacon associated with the specified point of care ID.
    /// </summary>
    /// <param name="pocId">The ID of the point of care for which to retrieve the color state.</param>
    /// <returns>A task representing the asynchronous operation, with the current color state of the light beacon.</returns>
    public async Task<LightBeaconColor> GetColor(ObjectId pocId)
    {
        var poc = await _pointOfCareService.FindById(pocId);
        if (poc == null) return LightBeaconColor.Off;
        return await GetColor(poc);
    }

    /// <summary>
    /// Retrieves the current color state of the light beacon associated with the specified point of care.
    /// </summary>
    /// <param name="poc">The point of care for which to retrieve the color state.</param>
    /// <returns>A task representing the asynchronous operation, with the current color state of the light beacon.</returns>
    public async Task<LightBeaconColor> GetColor(PointOfCare poc)
    {
        try
        {
            if (_locationsWithColor.TryGetValue(poc.Id, out var lightBeaconColor)) return lightBeaconColor;

            var lightBeaconConfig = poc.Configuration?.BeaconIdList;
            if (lightBeaconConfig == null || !lightBeaconConfig.Any()) return LightBeaconColor.Off;
            foreach (var beaconId in lightBeaconConfig)
            {
                var beacon = await GetBeacon(beaconId);
                if (beacon != null)
                    return await beacon.GetBeaconColor();
            }

            return LightBeaconColor.Off;
        }
        catch (Exception ex)
        {
            _logger.LogError("Exception getting color from beacon patient poc Id: {poc} Exception: {ex}", poc.Id, ex);
            return LightBeaconColor.Off;
        }
    }

    /// <summary>
    /// Sends a beacon broadcast message to all subscribers associated with the specified point of care, indicating the current color state of the beacon.
    /// </summary>
    /// <param name="poc">The point of care for which to send the color command.</param>
    /// <param name="color">The color to set on the light beacon.</param>
    /// <returns>A task representing the asynchronous operation.</returns>
    public async Task SendBeaconBroadcast(PointOfCare poc, LightBeaconColor color)
    {
        var subscribers = _subscribersService.GetSubscribers()
            .Where(s => s.LocationIds.Any(lid => lid == poc.Id))
            .ToList();
        BeaconResponse bc = new(color.ToString(), poc.Id, poc.UnitId);

        foreach (var subscriber in subscribers)
            await _clientMessageService.SendAsync(subscriber.Id, OperationType.Beacon, bc);
    }

    /// <summary>
    /// Sends a beacon broadcast message to all subscribers associated with the specified point of care ID, indicating the current color state of the beacon.
    /// </summary>
    /// <param name="pocId">The ID of the point of care for which to send the color command.</param>
    /// <param name="color">The color to set on the light beacon.</param>
    /// <returns>A task representing the asynchronous operation.</returns>
    public async Task SendBeaconBroadcast(ObjectId pocId, LightBeaconColor color)
    {
        var poc = await _pointOfCareService.FindById(pocId);
        if (poc == null) return;
        _ = SendBeaconBroadcast(poc, color);
    }

    /// <summary>
    /// Sends a raw message to the light beacon API using an HTTP POST request. 
    /// The method constructs the request body with the specified parameters, sends the request to the configured URL, and logs the result of the operation, including any errors that may occur during the process.
    /// </summary>
    /// <param name="body">The body of the message to send.</param>
    /// <returns>A task representing the asynchronous operation.</returns>
    private async Task SendMessage(string body)
    {
        if (string.IsNullOrEmpty(Url))
        {
            _logger.LogError("Url string is null or empty. Message:{body}", body);
            return;
        }

        HttpClient client = new();
        var content = new StringContent(body, Encoding.UTF8, "application/x-www-form-urlencoded");

        try
        {
            var response = await client.PostAsync(Url, content);

            if (response.IsSuccessStatusCode)
                _logger.LogDebug("Message sent successfully: {body}", body);
            else
                _logger.LogError("Error SendMessage {body}: {responseStatusCode}", body, response.StatusCode);
        }
        catch (Exception ex)
        {
            _logger.LogError("Error SendMessage {body}: {exMessage}", body, ex);
        }
    }

    /// <summary>
    /// Retrieves a light beacon device instance based on the specified light beacon ID. 
    /// The method queries the repository for the light beacon configuration, checks if the necessary options are present, and creates an instance of the appropriate light beacon device class based on the type specified in the configuration.
    /// </summary>
    /// <param name="lightBeaconId">The ID of the light beacon to retrieve.</param>
    /// <returns>A task representing the asynchronous operation, with a result of the light beacon device instance if found; otherwise, null.</returns>
    private async Task<LightBeaconDevice?> GetBeacon(ObjectId lightBeaconId)
    {
        var cfg = await _lightBeaconRepository.GetById(lightBeaconId);
        if (cfg == null) return null;
        if (string.IsNullOrEmpty(cfg.Options.Url) || cfg.Options.Port == null)
            return null;
        switch (cfg.Type)
        {
            case "TURKTBENS2":
                return new Turktbens2LightBeacon(new EquatableDictionary<string, object>
                {
                    { "url", cfg.Options.Url },
                    { "port", cfg.Options.Port },
                    { "password", cfg.Options.Password ?? "password" },
                    { "emulate", cfg.Options.Emulate }
                });
        }

        return null;
    }

    /// <summary>
    /// Converts the contents of a concurrent dictionary mapping point of care IDs to their associated light beacon colors into a string representation for logging purposes.
    /// </summary>
    /// <param name="dictionary"></param>
    /// <returns></returns>
    public static string DictionaryToString(ConcurrentDictionary<ObjectId, LightBeaconColor> dictionary)
    {
        var builder = new StringBuilder();

        foreach (var pair in dictionary) builder.AppendLine($"Location: {pair.Key}, Color: {pair.Value}");

        return builder.ToString();
    }


    //TODO:Almacenar en memoria los estados de los colores de la baliza
    //Array con el puerto y color
}