using System;
using System.Threading;
using SimpleCore.Library;
using StandardScene.Signal.Model;

namespace StandardScene.Signal.Plc
{
    /// <summary>
    /// 单机构 200ms 循环：心跳、读允许进出、回读写值、按需写入、自动离站。
    /// 对齐反编译 <c>PLCManager.ExcuteThreadMethod</c>。
    /// </summary>
    /// <remarks>
    /// 一拍顺序固定，失败则本拍标断线并 return，避免用脏数据推进状态机：
    /// 1. 翻转内存心跳标志；若配置了 HeartDb，读当前位再写反码（PLC 侧看翻转判断调度活着）。
    /// 2. 读 ReadDb → <see cref="PlcStationRuntime.ReadSignal"/>。
    /// 3. 回读 WriteDb → ReadWriteSignal；与目标 WriteSignal 一致则 WriteSuccess=true。
    /// 4. 目标与回读不一致且尚未成功时写入目标字节。
    /// 5. <see cref="PlcStationLogic.AutoLeave"/>。
    /// 读写均在 <see cref="PlcStationRuntime.Sync"/> 内，与扫车 Handler 互斥。
    /// </remarks>
    public sealed class PlcStationWorker : IDisposable
    {
        private readonly SiemensPlcSession _session;
        private readonly int _pollMs;
        private readonly Action _onTick;
        private CancellationTokenSource _cts;
        private Thread _thread;

        /// <summary>断线日志节流起点，避免 200ms 打满诊断。</summary>
        private DateTime _lastFailLog = DateTime.MinValue;

        public PlcStationWorker(PlcStationRuntime runtime, SiemensPlcSession session, int pollMs, Action onTick = null)
        {
            Runtime = runtime ?? throw new ArgumentNullException(nameof(runtime));
            _session = session ?? throw new ArgumentNullException(nameof(session));
            _pollMs = Math.Max(50, pollMs);
            _onTick = onTick;
        }

        /// <summary>本机构运行态，扫车线程按 JgName 查找后加 Sync 读写。</summary>
        public PlcStationRuntime Runtime { get; }

        /// <summary>启动后台线程。重复 Start 若仍存活则忽略。</summary>
        public void Start()
        {
            if (_thread != null && _thread.IsAlive) return;
            _cts = new CancellationTokenSource();
            _thread = new Thread(Loop)
            {
                IsBackground = true,
                Name = $"PlcStation:{Runtime.Config.JgName}"
            };
            _thread.Start();
            Diagnosis.Log($"{Runtime.Config.JgName} Worker 已启动 → {Runtime.Config.Ip}:{Runtime.Config.Port}", "PLC", true);
        }

        /// <summary>取消循环、最多等 2s、关闭 S7 连接并标断线。</summary>
        public void Stop()
        {
            try { _cts?.Cancel(); }
            catch { }
            try { _thread?.Join(2000); }
            catch { }
            _thread = null;
            _session.Close();
            Runtime.IsConnected = false;
            Diagnosis.Log($"{Runtime.Config.JgName} Worker 已停止", "PLC", true);
        }

        public void Dispose() => Stop();

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

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

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

        /// <summary>单拍 PLC 交互。心跳地址为空则跳过心跳；读写地址为空则跳过对应步骤。</summary>
        internal void Tick()
        {
            var cfg = Runtime.Config;
            Runtime.Heart = !Runtime.Heart;

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

            if (!string.IsNullOrWhiteSpace(cfg.ReadDb))
            {
                if (_session.TryReadByte(cfg.ReadDb, out var rb))
                    Runtime.ReadSignal = PlcStationLogic.ConvertRead(rb);
                else
                {
                    MarkDisconnected("读地址失败");
                    return;
                }
            }

            if (!string.IsNullOrWhiteSpace(cfg.WriteDb))
            {
                if (_session.TryReadByte(cfg.WriteDb, out var wb))
                {
                    Runtime.ReadWriteSignal = PlcStationLogic.ConvertWrite(wb);
                    if (Runtime.ReadWriteSignal == Runtime.WriteSignal)
                        Runtime.WriteSuccess = true;
                }
                else
                {
                    MarkDisconnected("写地址回读失败");
                    return;
                }

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

            Runtime.IsConnected = true;
            Runtime.LastError = "";
            PlcStationLogic.AutoLeave(Runtime);
        }

        private void MarkDisconnected(string reason)
        {
            Runtime.IsConnected = false;
            Runtime.LastError = reason;
            LogFail($"{Runtime.Config.JgName} {reason}");
        }

        /// <summary>同一机构失败日志最少间隔 5 秒。</summary>
        private void LogFail(string msg)
        {
            if ((DateTime.Now - _lastFailLog).TotalSeconds < 5) return;
            _lastFailLog = DateTime.Now;
            Diagnosis.Log(msg, "PLC", true);
        }
    }
}
