using Peak.Can.Basic;
using IOModuleTestBlazor.Models;
using IOModuleTestBlazor.Services;

namespace IOModuleTestBlazor;

/// <summary>
/// Background service that initialises the PCAN adapter and stores CAN messages
/// in the CanService for polling by UI components.
/// </summary>
public class CanWorker(
    ILogger<CanWorker> logger,
    IConfiguration configuration,
    ICanService canService) : BackgroundService
{
    public override Task StartAsync(CancellationToken cancellationToken)
    {
        // Seed filters and bitmasks from appsettings regardless of connection state
        foreach (var f in configuration.GetSection("CanOptions:Filters").Get<CanFilter[]>() ?? [])
            canService.AddFilter(f);
        foreach (var b in configuration.GetSection("CanOptions:Bitmasks").Get<CanBitmask[]>() ?? [])
            canService.AddBitmask(b);

        // Auto-connect if config is present; failures are non-fatal so the app
        // still starts and the user can configure the connection from the UI.
        try
        {
            var channel = ResolveChannel();
            var bitrate = ResolveBitrate();
            canService.Initialize(channel, bitrate);
            logger.LogInformation("CAN channel {Channel} ready at {Bitrate}", channel, bitrate);
        }
        catch (Exception ex)
        {
            logger.LogWarning(ex, "CAN auto-connect failed — configure via the UI.");
        }

        return base.StartAsync(cancellationToken);
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            // Pick up any reinit request from the UI
            if (canService.TryConsumePendingReinit(out var newChannel, out var newBitrate))
            {
                logger.LogInformation("Reinitializing CAN: {Channel} at {Bitrate}", newChannel, newBitrate);
                if (canService.IsConnected) canService.Uninitialize();
                try
                {
                    canService.Initialize(newChannel, newBitrate);
                    logger.LogInformation("CAN reinitialized: {Channel} at {Bitrate}", newChannel, newBitrate);
                }
                catch (Exception ex)
                {
                    logger.LogError(ex, "CAN reinitialization failed.");
                }
                continue;
            }

            if (!canService.IsConnected)
            {
                await Task.Delay(100, stoppingToken);
                continue;
            }

            // Process multiple messages in batch to drain buffer faster and reduce latency
            int messagesProcessed = 0;
            const int maxBatchSize = 10; // Process up to 10 messages per loop iteration
            
            while (messagesProcessed < maxBatchSize)
            {
                var result = canService.Read(out PcanMessage msg, out ulong timestamp);

                if (result == PcanStatus.OK)
                {
                    if (!canService.PassesFilter(msg.ID))
                    {
                        logger.LogDebug("[FILTERED] ID=0x{Id:X3} blocked by filter", msg.ID);
                        messagesProcessed++;
                        continue;
                    }

                    var data = new byte[msg.DLC];
                    for (int i = 0; i < msg.DLC; i++)
                        data[i] = msg.Data[i];

                    var signals = canService.ExtractSignals(msg.ID, data);

                    var messageDto = new CanMessageDto(
                        Id: msg.ID,
                        Data: data,
                        Dlc: msg.DLC,
                        TimestampUs: timestamp,
                        IsExtended: msg.MsgType == MessageType.Extended,
                        Signals: signals.Count > 0 ? signals : null
                    );

                    canService.UpdateLatestMessage(messageDto);

                    logger.LogDebug("[RX] ID=0x{Id:X3} DLC={Dlc} Data={Data} Published to UI",
                        msg.ID, msg.DLC, Convert.ToHexString(data));
                    
                    messagesProcessed++;
                }
                else if (result == PcanStatus.ReceiveQueueEmpty)
                {
                    // No more messages in buffer, exit batch processing
                    break;
                }
                else
                {
                    logger.LogError("CAN read error: {Error}", GetErrorText(result));
                    await Task.Delay(100, stoppingToken);
                    break;
                }
            }
            
            // Small yield to prevent 100% CPU usage when no messages
            if (messagesProcessed == 0)
            {
                await Task.Yield();
            }
        }
    }

    public override Task StopAsync(CancellationToken cancellationToken)
    {
        canService.Uninitialize();
        logger.LogInformation("CAN channel uninitialized.");
        return base.StopAsync(cancellationToken);
    }

    // ── Channel / bitrate resolution ──────────────────────────────────────────

    private PcanChannel ResolveChannel()
    {
        var name = configuration["CanOptions:Channel"];
        if (!string.IsNullOrWhiteSpace(name) &&
            Enum.TryParse(name, ignoreCase: true, out PcanChannel configured))
            return configured;

        logger.LogInformation("No channel configured — scanning for available PCAN USB channels...");
        var available = canService.GetAvailableChannels();

        if (available.Count == 0)
            throw new InvalidOperationException("No PCAN USB channels detected.");

        logger.LogInformation("Auto-selected channel: {Channel}", available[0]);
        return available[0];
    }

    private Bitrate ResolveBitrate()
    {
        var name = configuration["CanOptions:Bitrate"];
        if (!string.IsNullOrWhiteSpace(name) &&
            Enum.TryParse(name, ignoreCase: true, out Bitrate configured))
            return configured;

        logger.LogInformation("No bitrate configured — defaulting to 500 kbps.");
        return Bitrate.Pcan500;
    }

    private static string GetErrorText(PcanStatus status)
    {
        try { Api.GetErrorText(status, out var text); return text; }
        catch (PcanBasicException) { return status.ToString(); }
    }
}
