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

namespace IOModuleTestBlazor.Services;

public class CanService : ICanService
{
    private PcanChannel _channel;
    private readonly List<CanFilter> _filters = [];
    private readonly List<CanBitmask> _bitmasks = [];
    private readonly Dictionary<uint, CanMessageDto> _latestMessages = [];
    private readonly object _lock = new();

    public bool IsConnected { get; private set; }
    public string ChannelName => IsConnected ? _channel.ToString() : "";
    public Bitrate CurrentBitrate { get; private set; }

    private (PcanChannel Channel, Bitrate Bitrate)? _pendingReinit;

    public IReadOnlyDictionary<uint, CanMessageDto> GetLatestMessages()
    {
        lock (_lock) return new Dictionary<uint, CanMessageDto>(_latestMessages);
    }

    public void UpdateLatestMessage(CanMessageDto dto)
    {
        lock (_lock) _latestMessages[dto.Id] = dto;
    }

    // ── Filters ───────────────────────────────────────────────────────────────

    public IReadOnlyList<CanFilter> Filters
    {
        get { lock (_lock) return _filters.ToList(); }
    }

    public CanFilter AddFilter(CanFilter filter)
    {
        lock (_lock) _filters.Add(filter);
        return filter;
    }

    public bool RemoveFilter(Guid id)
    {
        lock (_lock) return _filters.RemoveAll(f => f.Id == id) > 0;
    }

    public void ClearFilters()
    {
        lock (_lock) _filters.Clear();
    }

    // ── Bitmasks ──────────────────────────────────────────────────────────────

    public IReadOnlyList<CanBitmask> Bitmasks
    {
        get { lock (_lock) return _bitmasks.ToList(); }
    }

    public CanBitmask AddBitmask(CanBitmask bitmask)
    {
        lock (_lock) _bitmasks.Add(bitmask);
        return bitmask;
    }

    public bool RemoveBitmask(Guid id)
    {
        lock (_lock) return _bitmasks.RemoveAll(b => b.Id == id) > 0;
    }

    public void ClearBitmasks()
    {
        lock (_lock) _bitmasks.Clear();
    }

    // ── Helpers ───────────────────────────────────────────────────────────────

    public bool PassesFilter(uint messageId)
    {
        lock (_lock)
        {
            if (_filters.Count == 0) return true;
            return _filters.Any(f => (messageId & f.Mask) == (f.MessageId & f.Mask));
        }
    }

    public IReadOnlyDictionary<string, double> ExtractSignals(uint messageId, byte[] data)
    {
        List<CanBitmask> applicable;
        lock (_lock)
            applicable = _bitmasks.Where(b => b.MessageId == messageId).ToList();

        if (applicable.Count == 0)
            return new Dictionary<string, double>();

        Span<byte> padded = stackalloc byte[8];
        var len = Math.Min(data.Length, 8);
        for (int i = 0; i < len; i++)
            padded[i] = data[i];

        ulong raw = 0;
        for (int i = 0; i < 8; i++)
            raw = (raw << 8) | padded[i];

        var signals = new Dictionary<string, double>(applicable.Count);
        foreach (var bm in applicable)
        {
            ulong masked = (raw & bm.DataMask) >> bm.RightShift;
            signals[bm.SignalName] = masked * bm.Scale + bm.Offset;
        }
        return signals;
    }

    // ── PCAN passthrough ──────────────────────────────────────────────────────

    public IReadOnlyList<PcanChannel> GetAvailableChannels()
    {
        var available = new List<PcanChannel>();
        foreach (var ch in Enum.GetValues<PcanChannel>()
            .Where(c => c.ToString().StartsWith("Usb", StringComparison.OrdinalIgnoreCase))
            .OrderBy(c => c.ToString()))
        {
            var result = Api.GetValue(ch, PcanParameter.ChannelCondition, out uint condition);
            if (result == PcanStatus.OK && condition != 0)
                available.Add(ch);
        }
        return available;
    }

    public void RequestReinitialize(PcanChannel channel, Bitrate bitrate)
    {
        lock (_lock) _pendingReinit = (channel, bitrate);
    }

    public bool TryConsumePendingReinit(out PcanChannel channel, out Bitrate bitrate)
    {
        lock (_lock)
        {
            if (_pendingReinit.HasValue)
            {
                channel = _pendingReinit.Value.Channel;
                bitrate = _pendingReinit.Value.Bitrate;
                _pendingReinit = null;
                return true;
            }
            channel = default;
            bitrate = default;
            return false;
        }
    }

    public void Initialize(PcanChannel channel, Bitrate bitrate)
    {
        _channel = channel;
        var result = Api.Initialize(channel, bitrate);
        if (result != PcanStatus.OK)
            throw new InvalidOperationException($"CAN init failed: {GetErrorText(result)}");
        
        // Optimize PCAN settings for low latency
        try
        {
            // Try to set receive event to 0 to disable buffering delays
            Api.SetValue(channel, PcanParameter.ReceiveEvent, 0u);
        }
        catch (Exception)
        {
            // Buffer optimization failed but connection still works
        }
        
        IsConnected = true;
        CurrentBitrate = bitrate;
    }

    public void Uninitialize()
    {
        Api.Uninitialize(_channel);
        IsConnected = false;
    }

    public PcanStatus Read(out PcanMessage msg, out ulong timestamp)
        => Api.Read(_channel, out msg, out timestamp);

    public PcanStatus Write(PcanMessage msg)
    {
        var status = Api.Write(_channel, msg);
        if (status == PcanStatus.OK)
        {
            var timestampUs = (ulong)(DateTime.UtcNow.Ticks / 10);
            var data = new byte[msg.DLC];
            Array.Copy(msg.Data, data, msg.DLC);
            UpdateLatestMessage(new CanMessageDto(
                msg.ID, data, msg.DLC, timestampUs,
                msg.MsgType == MessageType.Extended,
                null));
        }
        return status;
    }

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