using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using SimpleCore.Library;
using StandardScene.Signal.Model;

namespace StandardScene.Signal.Plc
{
    /// <summary>
    /// 单机构 200ms 循环：心跳、按工位读 BOOL（或旧一字节）、回写变化位、自动离站。
    /// </summary>
    public sealed class PlcStationWorker : IDisposable
    {
        private readonly SiemensPlcSession _session;
        private readonly int _pollMs;
        private readonly Action _onTick;
        private readonly PlcStationModel _station;
        private readonly List<PlcStationRuntime> _docks;
        private CancellationTokenSource _cts;
        private Thread _thread;
        private DateTime _lastFailLog = DateTime.MinValue;

        public PlcStationWorker(
            PlcStationModel station,
            IEnumerable<PlcStationRuntime> docks,
            SiemensPlcSession session,
            object sync,
            int pollMs,
            Action onTick = null)
        {
            _station = station ?? throw new ArgumentNullException(nameof(station));
            _docks = (docks ?? Array.Empty<PlcStationRuntime>()).Where(d => d != null).ToList();
            _session = session ?? throw new ArgumentNullException(nameof(session));
            Sync = sync ?? new object();
            _pollMs = Math.Max(50, pollMs);
            _onTick = onTick;
            foreach (var d in _docks)
                d.Heart = false;
        }

        public object Sync { get; }

        public IReadOnlyList<PlcStationRuntime> Docks => _docks;

        public PlcStationRuntime Runtime => _docks.Count > 0 ? _docks[0] : null;

        public void Start()
        {
            if (_thread != null && _thread.IsAlive) return;
            _cts = new CancellationTokenSource();
            _thread = new Thread(Loop)
            {
                IsBackground = true,
                Name = $"PlcStation:{_station.JgName}"
            };
            _thread.Start();
            Diagnosis.Log($"{_station.JgName} Worker 已启动 → {_station.Ip}:{_station.Port} 工位{_docks.Count}", "PLC", true);
        }

        public void Stop()
        {
            try { _cts?.Cancel(); }
            catch { }
            try { _thread?.Join(2000); }
            catch { }
            _thread = null;
            _session.Close();
            foreach (var d in _docks)
                d.IsConnected = false;
            Diagnosis.Log($"{_station.JgName} Worker 已停止", "PLC", true);
        }

        public void Dispose() => Stop();

        private void Loop()
        {
            var token = _cts.Token;
            while (!token.IsCancellationRequested)
            {
                try
                {
                    lock (Sync)
                        Tick();
                }
                catch (Exception ex)
                {
                    foreach (var d in _docks)
                    {
                        d.LastError = ex.Message;
                        d.IsConnected = false;
                    }
                    LogFail($"{_station.JgName} 周期异常 {ex.Message}");
                }

                try { _onTick?.Invoke(); }
                catch { }

                try { Thread.Sleep(_pollMs); }
                catch (ThreadInterruptedException) { break; }
            }
        }

        internal void Tick()
        {
            var heart = !_docks.Select(d => d.Heart).FirstOrDefault();
            foreach (var d in _docks)
                d.Heart = heart;

            var heartAddr = PlcConfigLoader.ResolveHeartAddress(_station);
            if (!string.IsNullOrWhiteSpace(heartAddr))
            {
                if (_session.TryReadBool(heartAddr, out var bit))
                {
                    _session.TryWriteBool(heartAddr, !bit);
                    Thread.Sleep(20);
                }
                else
                {
                    MarkDisconnected("心跳读写失败");
                    return;
                }
            }

            var boolDocks = _docks.Where(d => !d.Dock.UseByteIo).ToList();
            var byteDocks = _docks.Where(d => d.Dock.UseByteIo).ToList();

            if (boolDocks.Count > 0 && !TickBoolDocks(boolDocks))
                return;

            foreach (var rt in byteDocks)
            {
                if (!TickByteDock(rt))
                    return;
            }

            foreach (var rt in _docks)
            {
                rt.IsConnected = true;
                rt.LastError = "";
                PlcStationLogic.AutoLeave(rt);
            }
        }

        private bool TickBoolDocks(List<PlcStationRuntime> docks)
        {
            var db = PlcConfigLoader.ResolveDb(_station);
            var bytes = new List<int>();
            foreach (var rt in docks)
                CollectBytes(rt.Dock, bytes);
            if (bytes.Count == 0)
                return true;

            var origin = bytes.Min();
            var last = bytes.Max();
            var len = (ushort)(last - origin + 1);
            if (!_session.TryReadBytes(PlcAddress.Byte(db, origin), len, out var buffer) ||
                buffer == null || buffer.Length < len)
            {
                MarkDisconnected("BOOL 块读失败");
                return false;
            }

            var changedBytes = new HashSet<int>();
            foreach (var rt in docks)
            {
                var dock = rt.Dock;
                var allowIn = PlcAddress.GetBit(buffer, origin, dock.AllowInByte, dock.AllowInBit);
                var allowOut = PlcAddress.GetBit(buffer, origin, dock.AllowOutByte, dock.AllowOutBit);
                rt.ReadSignal = allowIn
                    ? SignalReadValue.允许进入
                    : allowOut
                        ? SignalReadValue.允许离开
                        : SignalReadValue.无状态;

                var readWrite = DecodeWrite(buffer, origin, dock);
                rt.ReadWriteSignal = readWrite;
                if (readWrite == rt.WriteSignal)
                    rt.WriteSuccess = true;

                if (rt.WriteSignal != readWrite && !rt.WriteSuccess)
                {
                    Diagnosis.Log(
                        $"AGV→机构 {rt.DisplayName} 写入【{rt.WriteSignal}】请求进入{rt.RequestInAgvNo} 到达{rt.InAgvNo}",
                        "JGControl", true);
                    EncodeWrite(buffer, origin, dock, rt.WriteSignal, changedBytes);
                }
            }

            foreach (var abs in changedBytes)
            {
                var i = abs - origin;
                if (i < 0 || i >= buffer.Length)
                    continue;
                if (!_session.TryWriteByte(PlcAddress.Byte(db, abs), buffer[i]))
                {
                    MarkDisconnected("BOOL 写失败");
                    return false;
                }
            }

            return true;
        }

        private bool TickByteDock(PlcStationRuntime rt)
        {
            var cfg = rt.Dock;
            if (!string.IsNullOrWhiteSpace(cfg.ReadDb))
            {
                if (_session.TryReadByte(cfg.ReadDb, out var rb))
                    rt.ReadSignal = PlcStationLogic.ConvertRead(rb);
                else
                {
                    MarkDisconnected($"{rt.DisplayName} 读地址失败");
                    return false;
                }
            }

            if (string.IsNullOrWhiteSpace(cfg.WriteDb))
                return true;

            if (!_session.TryReadByte(cfg.WriteDb, out var wb))
            {
                MarkDisconnected($"{rt.DisplayName} 写地址回读失败");
                return false;
            }

            rt.ReadWriteSignal = PlcStationLogic.ConvertWrite(wb);
            if (rt.ReadWriteSignal == rt.WriteSignal)
                rt.WriteSuccess = true;

            if (rt.WriteSignal != rt.ReadWriteSignal && !rt.WriteSuccess)
            {
                Diagnosis.Log(
                    $"AGV→机构 {rt.DisplayName} 写入【{rt.WriteSignal}】请求进入{rt.RequestInAgvNo} 到达{rt.InAgvNo}",
                    "JGControl", true);
                _session.TryWriteByte(cfg.WriteDb, (byte)rt.WriteSignal);
            }

            return true;
        }

        private static void CollectBytes(PlcStationDockModel dock, List<int> bytes)
        {
            if (dock == null) return;
            bytes.Add(dock.AllowInByte);
            bytes.Add(dock.AllowOutByte);
            bytes.Add(dock.EnteringByte);
            bytes.Add(dock.ArrivedByte);
            bytes.Add(dock.LeavingByte);
            bytes.Add(dock.LeftByte);
        }

        private static SignalWriteValue DecodeWrite(byte[] buffer, int origin, PlcStationDockModel dock)
        {
            if (PlcAddress.GetBit(buffer, origin, dock.LeftByte, dock.LeftBit))
                return SignalWriteValue.已离开;
            if (PlcAddress.GetBit(buffer, origin, dock.LeavingByte, dock.LeavingBit))
                return SignalWriteValue.离开中;
            if (PlcAddress.GetBit(buffer, origin, dock.ArrivedByte, dock.ArrivedBit))
                return SignalWriteValue.到位;
            if (PlcAddress.GetBit(buffer, origin, dock.EnteringByte, dock.EnteringBit))
                return SignalWriteValue.进入中;
            return SignalWriteValue.无状态;
        }

        private static void EncodeWrite(
            byte[] buffer,
            int origin,
            PlcStationDockModel dock,
            SignalWriteValue value,
            HashSet<int> changedBytes)
        {
            void Set(int abs, int bit, bool on)
            {
                var before = PlcAddress.GetBit(buffer, origin, abs, bit);
                if (before == on) return;
                PlcAddress.SetBit(buffer, origin, abs, bit, on);
                changedBytes.Add(abs);
            }

            Set(dock.EnteringByte, dock.EnteringBit, value == SignalWriteValue.进入中);
            Set(dock.ArrivedByte, dock.ArrivedBit, value == SignalWriteValue.到位);
            Set(dock.LeavingByte, dock.LeavingBit, value == SignalWriteValue.离开中);
            Set(dock.LeftByte, dock.LeftBit, value == SignalWriteValue.已离开);
        }

        private void MarkDisconnected(string reason)
        {
            foreach (var d in _docks)
            {
                d.IsConnected = false;
                d.LastError = reason;
            }
            LogFail($"{_station.JgName} {reason}");
        }

        private void LogFail(string msg)
        {
            if ((DateTime.Now - _lastFailLog).TotalSeconds < 5)
                return;
            _lastFailLog = DateTime.Now;
            Diagnosis.Log(msg, "PLC", true);
        }
    }
}
