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 object _lock = new();

    public bool IsConnected { get; private set; }
    public string ChannelName => _channel.ToString();

    public event Action<CanMessageDto>? MessageReceived;

    public void PublishMessage(CanMessageDto dto) => MessageReceived?.Invoke(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 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)}");
        IsConnected = true;
    }

    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)
        => Api.Write(_channel, msg);

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