#if UNITY_IOS
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Threading;
using UnityEngine;
using UnityEngine.Events;

namespace Aidlab.BLE
{
    public class IosBLEConnector : 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 IosBLEConnector current;
        private static readonly IosBLEApi.DataCallback DataCallback = OnData;
        private static readonly IosBLEApi.EventCallback EventCallback = OnEvent;

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

            current = this;
            IosBLEApi.SetDataCallback(DataCallback);
            IosBLEApi.SetEventCallback(EventCallback);
            IosBLEApi.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;
            IosBLEApi.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($"IosBLEConnector: RX backlog too large; processed {processed} chunks this frame");
            }
            else if (sw.ElapsedMilliseconds >= 4 && processed > 0)
            {
                UnityEngine.Debug.LogWarning($"IosBLEConnector: RX processing time budget exceeded; processed {processed} chunks this frame");
            }
            if (droppedRxChunks > 0)
            {
                UnityEngine.Debug.LogWarning($"IosBLEConnector: 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 = IosBLEApi.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 = IosBLEApi.Write(chunk, chunk.Length);
                chunkIndex++;
                if (!ok)
                {
                    UnityEngine.Debug.LogWarning($"IosBLEConnector: 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)
        {
            IosBLEConnector instance = current;
            if (instance == null)
                return;

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

            if (!instance.timeSetConfirmed)
                return;

            byte[] chunk = new byte[size];
            Marshal.Copy(data, chunk, 0, size);
            lock (instance.rxQueue)
            {
                if (instance.rxQueue.Count >= 16384)
                {
                    instance.rxQueue.Dequeue();
                    instance.droppedRxChunks++;
                }
                instance.rxQueue.Enqueue(chunk);
            }
        }

        private static void OnEvent(int evt, IntPtr messagePtr)
        {
            IosBLEConnector 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 (iOS): {message}");
                    break;
                case 6: // debug
                    if (!string.IsNullOrEmpty(message) &&
                        message.IndexOf("Set time sent", StringComparison.OrdinalIgnoreCase) >= 0)
                    {
                        instance.timeSetAttempted = true;
                    }
                    break;
                case 7: // time set confirmed (or failed)
                    if (!string.IsNullOrEmpty(message) &&
                        message.IndexOf("failed", StringComparison.OrdinalIgnoreCase) >= 0)
                    {
                        instance.timeSetFailed = true;
                        UnityEngine.Debug.LogWarning($"Aidlab BLE (iOS): {message}");
                        break;
                    }

                    instance.timeSetConfirmed = true;
                    if (instance.isConnected && instance.firmwareReceived)
                        instance.MaybeNotifyConnected();
                    break;
                default:
                    break;
            }
        }

        private void MaybeNotifyConnected()
        {
            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 (timeSetAttempted)
            {
                if (elapsedMs < 10000)
                    return;

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

            if (elapsedMs < 2000)
                return;

            timeSetConfirmed = true;
            UnityEngine.Debug.LogWarning("Aidlab BLE (iOS): 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 IosBLEConnector : IBleConnector
    {
        public IosBLEConnector(AidlabSDK _, string __) { }
        public void ConnectionProcess() { }
        public void Disconnect() { }
        public void Write(byte[] payload) { }
    }
}
#endif

