#if UNITY_STANDALONE_OSX || UNITY_EDITOR_OSX
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Threading;
using UnityEngine;
using UnityEngine.Events;

namespace Aidlab.BLE
{
    public class MacBLEConnector : IBleConnector
    {
        private readonly AidlabSDK aidlabSdk;
        private readonly string deviceNameToConnect;

        private volatile bool isConnected;
        private volatile bool firmwareReceived;
        private volatile bool timeSetConfirmed;
        private volatile bool timeSetAttempted;
        private volatile bool timeSetFailed;
        private int connectedAtTick = -1;

        private readonly Queue<byte[]> rxQueue = new Queue<byte[]>();
        private int droppedRxChunks;

        private readonly Queue<byte[]> writeQueue = new Queue<byte[]>();
        private readonly AutoResetEvent writeSignal = new AutoResetEvent(false);
        private Thread writerThread;
        private volatile bool writerRunning = true;

        public bool IsConnected => isConnected;
        public bool HasFirmwareRevision => firmwareReceived;

        private static MacBLEConnector current;
        private static readonly MacBLEApi.DataCallback DataCallback = OnData;
        private static readonly MacBLEApi.EventCallback EventCallback = OnEvent;

        public MacBLEConnector(AidlabSDK aidlabSdk, string deviceNameToConnect)
        {
            this.aidlabSdk = aidlabSdk;
            this.deviceNameToConnect = deviceNameToConnect;

            current = this;
            MacBLEApi.SetDataCallback(DataCallback);
            MacBLEApi.SetEventCallback(EventCallback);
            MacBLEApi.Start(deviceNameToConnect);

            writerThread = new Thread(WriterLoop);
            writerThread.IsBackground = true;
            writerThread.Start();
        }

        public void ConnectionProcess()
        {
            FlushRxQueue();
            MaybeNotifyConnectedAfterTimeout();
        }

        public void Disconnect()
        {
            isConnected = false;
            writerRunning = false;
            writeSignal.Set();
            current = null;
            MacBLEApi.Stop();
            if (writerThread != null && writerThread.IsAlive)
            {
                writerThread.Join(500);
            }
        }

        public void Write(byte[] payload)
        {
            if (payload == null || payload.Length == 0)
                return;

            lock (writeQueue)
            {
                writeQueue.Enqueue(payload);
            }
            writeSignal.Set();
        }

        private void WriterLoop()
        {
            while (writerRunning)
            {
                writeSignal.WaitOne();

                while (true)
                {
                    byte[] payload = null;
                    lock (writeQueue)
                    {
                        if (writeQueue.Count > 0)
                            payload = writeQueue.Dequeue();
                    }

                    if (payload == null)
                        break;

                    if (!isConnected)
                        continue;

                    SendChunked(payload);
                }
            }
        }

        private void FlushRxQueue()
        {
            int processed = 0;
            const int maxPerFrame = 512;
            var sw = System.Diagnostics.Stopwatch.StartNew();

            while (processed < maxPerFrame)
            {
                byte[] chunk = null;
                lock (rxQueue)
                {
                    if (rxQueue.Count > 0)
                        chunk = rxQueue.Dequeue();
                }

                if (chunk == null)
                    break;

                aidlabSdk.OnAidlabDataReceived(chunk, chunk.Length);
                processed++;

                if (sw.ElapsedMilliseconds >= 4)
                    break;
            }

            if (processed >= maxPerFrame)
            {
                UnityEngine.Debug.LogWarning($"MacBLEConnector: RX backlog too large; processed {processed} chunks this frame");
            }
            else if (sw.ElapsedMilliseconds >= 4 && processed > 0)
            {
                UnityEngine.Debug.LogWarning($"MacBLEConnector: RX processing time budget exceeded; processed {processed} chunks this frame");
            }
            if (droppedRxChunks > 0)
            {
                UnityEngine.Debug.LogWarning($"MacBLEConnector: dropped {droppedRxChunks} RX chunks (main thread couldn't keep up)");
                droppedRxChunks = 0;
            }
        }

        private void SendChunked(byte[] payload)
        {
            int chunkSize = 20;
            int major = ParseMajorFirmware(aidlabSdk?.firmwareRevisionStr);
            if (major >= 4)
            {
                int max = MacBLEApi.GetMaxWriteLength();
                if (max > 0)
                    chunkSize = Math.Min(max, 514);
            }

            int offset = 0;
            int chunkIndex = 0;
            while (offset < payload.Length)
            {
                int size = Math.Min(chunkSize, payload.Length - offset);
                byte[] chunk = new byte[size];
                Buffer.BlockCopy(payload, offset, chunk, 0, size);
                bool ok = MacBLEApi.Write(chunk, chunk.Length);
                chunkIndex++;
                if (!ok)
                {
                    UnityEngine.Debug.LogWarning($"MacBLEConnector: Write failed (chunk #{chunkIndex}, {size}B)");
                    break;
                }
                offset += size;

                if (offset < payload.Length)
                    Thread.Sleep(2);
            }
        }

        private static int ParseMajorFirmware(string instance)
        {
            if (string.IsNullOrEmpty(instance))
                return 0;
            int dot = instance.IndexOf('.');
            string majorStr = dot >= 0 ? instance.Substring(0, dot) : instance;
            if (int.TryParse(majorStr, out int major))
                return major;
            return 0;
        }

        private static void OnData(IntPtr data, int size)
        {
            MacBLEConnector instance = current;
            if (instance == null)
                return;

            if (data == IntPtr.Zero || size <= 0)
                return;

            // Don't feed any bytes into the C++ core until we know device time was set.
            // Otherwise we can ingest sessions created under the default RTC epoch (1971) and poison SessionProcessor.
            if (!instance.timeSetConfirmed)
                return;

            byte[] chunk = new byte[size];
            Marshal.Copy(data, chunk, 0, size);
            lock (instance.rxQueue)
            {
                // Ensure CoreBluetooth thread never calls into C++ core directly.
                // AidlabSDK expects serialized access (create/destroy/send/receive) on Unity main thread.
                if (instance.rxQueue.Count >= 16384)
                {
                    instance.rxQueue.Dequeue();
                    instance.droppedRxChunks++;
                }
                instance.rxQueue.Enqueue(chunk);
            }
        }

        private static void OnEvent(int evt, IntPtr messagePtr)
        {
            MacBLEConnector instance = current;
            if (instance == null)
                return;

            string message = messagePtr == IntPtr.Zero ? "" : Marshal.PtrToStringAnsi(messagePtr);
            switch (evt)
            {
                case 1: // connected
                    instance.isConnected = true;
                    instance.connectedAtTick = Environment.TickCount;
                    instance.timeSetConfirmed = false;
                    instance.timeSetAttempted = false;
                    instance.timeSetFailed = false;
                    if (instance.firmwareReceived)
                        instance.MaybeNotifyConnected();
                    break;
                case 2: // disconnected
                    instance.isConnected = false;
                    EnqueueMainThread(() => instance.aidlabSdk.OnAidlabDisconnected());
                    break;
                case 3: // firmware
                    instance.aidlabSdk.firmwareRevisionStr = message;
                    instance.firmwareReceived = true;
                    if (instance.isConnected)
                        instance.MaybeNotifyConnected();
                    break;
                case 4: // hardware
                    instance.aidlabSdk.hardwareRevisionStr = message;
                    break;
                case 5: // error
                    UnityEngine.Debug.LogWarning($"Aidlab BLE (macOS): {message}");
                    break;
                case 6: // debug
                    UnityEngine.Debug.Log($"Aidlab BLE (macOS): {message}");
                    if (!string.IsNullOrEmpty(message) &&
                        message.IndexOf("Set time sent", StringComparison.OrdinalIgnoreCase) >= 0)
                    {
                        instance.timeSetAttempted = true;
                    }
                    if (!string.IsNullOrEmpty(message) &&
                        message.IndexOf("Time set confirmed", StringComparison.OrdinalIgnoreCase) >= 0)
                    {
                        instance.timeSetConfirmed = true;
                        if (instance.isConnected && instance.firmwareReceived)
                            instance.MaybeNotifyConnected();
                    }
                    break;
                case 7: // time set confirmed (or failed, but proceed anyway)
                    if (!string.IsNullOrEmpty(message) &&
                        message.IndexOf("failed", StringComparison.OrdinalIgnoreCase) >= 0)
                    {
                        instance.timeSetFailed = true;
                        UnityEngine.Debug.LogWarning($"Aidlab BLE (macOS): {message}");
                        // Don't proceed; without time, v3/v4 session timestamps may be invalid (1971).
                        break;
                    }

                    instance.timeSetConfirmed = true;
                    if (instance.isConnected && instance.firmwareReceived)
                        instance.MaybeNotifyConnected();
                    break;
                default:
                    if (!string.IsNullOrEmpty(message))
                        UnityEngine.Debug.Log($"Aidlab BLE (macOS): evt={evt} msg={message}");
                    break;
            }
        }

        private void MaybeNotifyConnected()
        {
            // Python sets time (2A2B) before starting collect; do the same when possible.
            // If time isn't available on a given device/firmware, we'll fall back after a short timeout.
            if (!timeSetConfirmed)
                return;

            EnqueueMainThread(() => aidlabSdk.OnAidlabConnected());
        }

        private void MaybeNotifyConnectedAfterTimeout()
        {
            if (!isConnected || !firmwareReceived || timeSetConfirmed || timeSetFailed)
                return;

            if (connectedAtTick < 0)
                return;

            int elapsedMs = unchecked(Environment.TickCount - connectedAtTick);
            // If we attempted to set time (CTS present), be strict: don't proceed without confirmation.
            // Proceeding would ingest sessions timestamped with the default RTC epoch (1971) and break sync.
            if (timeSetAttempted)
            {
                if (elapsedMs < 10000)
                    return;

                timeSetFailed = true;
                UnityEngine.Debug.LogWarning("Aidlab BLE (macOS): time set not confirmed within 10s; disconnecting");
                EnqueueMainThread(() =>
                {
                    aidlabSdk.OnAidlabDisconnected();
                    Disconnect();
                });
                return;
            }

            if (elapsedMs < 2000)
                return;

            // No CTS on this device/firmware - allow higher layers to proceed.
            timeSetConfirmed = true;
            UnityEngine.Debug.LogWarning("Aidlab BLE (macOS): time service not available; proceeding without set time");
            EnqueueMainThread(() => aidlabSdk.OnAidlabConnected());
        }

        private static void EnqueueMainThread(Action action)
        {
            if (action == null)
                return;
            var evt = new UnityEvent();
            evt.AddListener(() => action());
            MainThreadWorker.Enqueue(evt);
        }
    }
}
#else
namespace Aidlab.BLE
{
    public class MacBLEConnector : IBleConnector
    {
        public MacBLEConnector(AidlabSDK _, string __) { }
        public void ConnectionProcess() { }
        public void Disconnect() { }
        public void Write(byte[] payload) { }
    }
}
#endif
