using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using LessokajiWeaverUtilities.Utilities;
using Newtonsoft.Json;
using SimpleCore;
using SimpleCore.Library;
using SimpleLite.RCS;
using StandardScene.Signal.Logic;
using StandardScene.Signal.Model;
using StandardScene.Signal.Plc;
using StandardScene.Signal.Ui;
using StandardScene.Utils;

namespace StandardScene.Signal
{
    /// <summary>
    /// 第三方信号交互进程（场景 <c>scene.signal</c> 的唯一 Mission）。
    /// </summary>
    /// <remarks>
    /// <para>
    /// 对齐梅山吉利反编译插件 <c>CoreClass30.A20050.Plugin.DefaultPlugin</c>：
    /// 一边用西门子 S7 和机构 PLC 交换进出站字节，一边按站点对停稳 AGV 做握手 / 放行。
    /// </para>
    /// <para>
    /// 两条独立循环，不要合成一次 HTTP 或任务编排节点：
    /// </para>
    /// <list type="bullet">
    /// <item>
    /// <term>PLC Worker</term>
    /// <description>
    /// 每个机构一条后台线程，默认 200ms：心跳翻转、读「允许进入/离开」、回读写字节、按需写入、
    /// <see cref="PlcStationLogic.AutoLeave"/> 推进离站状态机。
    /// </description>
    /// </item>
    /// <item>
    /// <term>扫车线程</term>
    /// <description>
    /// <see cref="CarScanWorker"/> 默认 1s 扫全场车。停稳则按握手点类型分发给
    /// JGControl / PointControl / TimedCharging / RateFlowControl，再执行放行点；
    /// 行驶中仅对 JGControl 的 RequestIn 做「未允许进入则急停」。
    /// </description>
    /// </item>
    /// </list>
    /// <para>
    /// 配置三张 JSON（工作目录 <c>Config/Signal/</c>，可由迷毂数据中心改，保存后点「重新加载配置」）：
    /// 机构 <c>stations.json</c>、握手点 <c>handshake-points.json</c>、放行点 <c>release-points.json</c>。
    /// 站点 fields 若已写 <c>SignalType</c>/<c>SignalParams</c>，优先于握手点表。
    /// </para>
    /// <para>
    /// CycleGUI 进程工具栏必须点「信号交互进程」，不要打开开放泛型编辑器。
    /// 列表窗体只改 JSON，不经过本进程的 HTTP；改完需本进程已启动才会重启 Worker。
    /// </para>
    /// </remarks>
    [MissionType(Name = "信号交互进程", editor = typeof(SignalInteractMission))]
    [I18N.DocumentTranslation(Name = "Signal Interact Mission", locale = "en")]
    public class SignalInteractMission : Mission
    {
        /// <summary>扫车周期（毫秒）。对齐反编译 DefaultPlugin 主循环，现场一般保持 1000。</summary>
        [FieldMember] public int TickMs = 1000;

        /// <summary>单机构 PLC 轮询周期（毫秒）。对齐 PLCManager，现场一般保持 200。</summary>
        [FieldMember] public int PlcPollMs = 200;

        /// <summary>
        /// 西门子系列，传给 IoTClient <c>SiemensVersion</c>。
        /// 常用 <c>S7_1500</c> / <c>S7_1200</c> / <c>S7_300</c>；连字符会当成下划线解析。
        /// </summary>
        [FieldMember] public string PlcVersion = "S7_1500";

        /// <summary>机构未单独填 Port 时使用的 S7 端口，默认 102。</summary>
        [FieldMember] public int PlcPort = 102;

        /// <summary>S7 机架槽号，S7-1500 常见为 1；写入 <see cref="SiemensPlcSession"/>。</summary>
        [FieldMember] public int PlcSlot = 1;

        /// <summary>机构表路径。相对路径只解析工作目录 <c>Config/Signal</c>，不读插件目录。</summary>
        [FieldMember] public string StationsPath = @"Config\Signal\stations.json";

        /// <summary>放行点表路径。车停在这些站点且启用时，无条件停充并启动，不读 PLC。</summary>
        [FieldMember] public string ReleasePointsPath = @"Config\Signal\release-points.json";

        /// <summary>握手点表路径。按站点配置 JGControl / PointControl / TimedCharging / RateFlowControl。</summary>
        [FieldMember] public string HandshakePointsPath = @"Config\Signal\handshake-points.json";

        /// <summary>Execute 成功后为 true；ReloadConfig 仅在已启动时重启 Worker / 扫车。</summary>
        [JsonIgnore] private bool _started;

        /// <summary>保护 <see cref="_workers"/>：启动、停止、状态快照、扫车查找机构时共用。</summary>
        [JsonIgnore] private readonly object _workerLock = new object();

        /// <summary>当前在跑的机构 Worker，与 stations.json 一一对应（空名称的机构不会创建）。</summary>
        [JsonIgnore] private readonly List<PlcStationWorker> _workers = new List<PlcStationWorker>();

        /// <summary>全场扫车握手线程；停止进程时置空。</summary>
        [JsonIgnore] private CarScanWorker _scan;

        /// <summary>PLC 每 tick 都会回调刷新状态；用计数把 UI 刷新降到约每 5 个 poll 一次。</summary>
        [JsonIgnore] private int _statusTick;

        /// <summary>进程监视面板展示的运行摘要（机构连接数、扫车次数、各站读写字节）。</summary>
        public class SignalInteractMissionStatus : MissionStatus
        {
            /// <summary>当前 Worker 数量，等于已加载且名称非空的机构数。</summary>
            public int StationCount { get; set; }

            /// <summary>最近一次快照里 <c>IsConnected == true</c> 的机构数。</summary>
            public int ConnectedCount { get; set; }

            /// <summary>放行点 JSON 条数（含未启用）。</summary>
            public int ReleasePointCount { get; set; }

            /// <summary>握手点 JSON 条数。</summary>
            public int HandshakePointCount { get; set; }

            /// <summary><see cref="CarScanWorker.TickCount"/>，用于确认扫车线程是否在跑。</summary>
            public int ScanTickCount { get; set; }

            /// <summary>实际解析到的机构文件绝对路径。</summary>
            public string StationsFile { get; set; }

            /// <summary>实际解析到的放行点文件绝对路径。</summary>
            public string ReleasePointsFile { get; set; }

            /// <summary>实际解析到的握手点文件绝对路径。</summary>
            public string HandshakePointsFile { get; set; }

            /// <summary>各机构运行态快照，供监视列表展示。</summary>
            public List<PlcStationSnapshot> Stations { get; set; } = new List<PlcStationSnapshot>();
        }

        public override MissionStatus status { get; set; } = new SignalInteractMissionStatus();

        /// <summary>SimpleLite 按 MissionType 反射创建实例时调用。</summary>
        public static Mission Create() => new SignalInteractMission();

        /// <summary>
        /// 加载三张 JSON，拉起全部 PLC Worker 和扫车线程。
        /// 重复点击会先停再建；失败时 status 为「启动失败」并记诊断。
        /// </summary>
        [MethodMember(Name = "启动进程", Description = "加载配置，启动 PLC Worker 与扫车握手")]
        public override void Execute()
        {
            try
            {
                ReloadConfig();
                _started = true;
                RestartWorkers();
                RestartScan();
                var st = (SignalInteractMissionStatus)status;
                Diagnosis.Post(
                    $"信号交互进程已启动，机构={st.StationCount}，握手点={st.HandshakePointCount}，放行点={st.ReleasePointCount}",
                    "Signal", true);
            }
            catch (Exception ex)
            {
                status.status = "启动失败";
                Diagnosis.Post($"信号交互进程启动失败：{ExceptionFormatter.FormatEx(ex)}", "Signal", true);
                throw;
            }
        }

        /// <summary>
        /// 重新读 JSON 并刷新 status 里的路径/条数。
        /// 进程已启动时：重建 PLC Worker，并向扫车线程热替换握手点/放行点（不必停进程）。
        /// 迷毂数据中心改完文件后应调本方法，否则内存里仍是旧表。
        /// </summary>
        [MethodMember(Name = "重新加载配置", Description = "重新读取 JSON；若进程已启动则重启 Worker/扫车")]
        public void ReloadConfig()
        {
            var stationsFile = SignalConfigStore.Resolve(StationsPath);
            var releaseFile = SignalConfigStore.Resolve(ReleasePointsPath);
            var handshakeFile = SignalConfigStore.Resolve(HandshakePointsPath);
            var stations = SignalConfigStore.Load<PlcStationModel>(stationsFile);
            var points = SignalConfigStore.Load<ReleasePointModel>(releaseFile);
            var handshake = SignalConfigStore.Load<HandshakePointModel>(handshakeFile);

            var st = (SignalInteractMissionStatus)status;
            st.StationCount = stations.Count;
            st.ReleasePointCount = points.Count;
            st.HandshakePointCount = handshake.Count;
            st.StationsFile = stationsFile;
            st.ReleasePointsFile = releaseFile;
            st.HandshakePointsFile = handshakeFile;
            st.Stations = stations.Select(s => new PlcStationSnapshot { JgName = s.JgName }).ToList();
            status.status = _started ? "运行中" : "配置已加载";
            Diagnosis.Post(
                $"信号配置已加载：机构 {stations.Count}；握手点 {handshake.Count} @ {handshakeFile}；放行点 {points.Count} @ {releaseFile}",
                "Signal", true);

            if (_started)
            {
                RestartWorkers();
                _scan?.ReplaceConfig(handshake, points);
            }
        }

        /// <summary>停扫车、停全部 PLC 连接。隐藏了基类 Stop，工具栏显示本方法。</summary>
        [MethodMember(Name = "停止进程")]
        public new void Stop()
        {
            _started = false;
            try { _scan?.Stop(); }
            catch { }
            _scan = null;
            StopWorkers();
            status.status = "已停止";
            Diagnosis.Post("信号交互进程已停止", "Signal", true);
        }

        /// <summary>CycleGUI 独立窗体编辑机构表；保存立刻写 JSON，需「重新加载配置」才进 Worker。</summary>
        [MethodMember(Name = "打开机构列表", Description = "按 PlcStationModel 增删改机构配置")]
        public void OpenStations()
        {
            try
            {
                var path = SignalConfigStore.Resolve(StationsPath);
                ModelListPanel<PlcStationModel>.Open("PLC 机构配置", path, x => x?.JgName ?? "");
            }
            catch (Exception ex)
            {
                CycleUiHelper.Alert("错误", $"打开机构列表失败：{ex.Message}");
            }
        }

        /// <summary>CycleGUI 独立窗体编辑握手点；主键展示为「站点号 + 信号类型」。</summary>
        [MethodMember(Name = "打开握手点列表", Description = "按站点配置 JGControl / PointControl / TimedCharging / RateFlowControl")]
        public void OpenHandshakePoints()
        {
            try
            {
                var path = SignalConfigStore.Resolve(HandshakePointsPath);
                ModelListPanel<HandshakePointModel>.Open(
                    "握手点配置",
                    path,
                    x => x == null ? "" : $"{x.Sit} {x.SignalType}");
            }
            catch (Exception ex)
            {
                CycleUiHelper.Alert("错误", $"打开握手点列表失败：{ex.Message}");
            }
        }

        /// <summary>CycleGUI 独立窗体编辑放行点；主键为站点号。</summary>
        [MethodMember(Name = "打开放行点列表", Description = "按 ReleasePointModel 增删改放行点")]
        public void OpenReleasePoints()
        {
            try
            {
                var path = SignalConfigStore.Resolve(ReleasePointsPath);
                ModelListPanel<ReleasePointModel>.Open("放行点配置", path, x => x == null ? "" : x.Sit.ToString());
            }
            catch (Exception ex)
            {
                CycleUiHelper.Alert("错误", $"打开放行点列表失败：{ex.Message}");
            }
        }

        /// <summary>
        /// 扫车 Handler 用机构名查找运行态。返回拷贝列表，避免长时间持锁；
        /// 运行态对象本身仍由各 Worker 持有，读写需 <see cref="PlcStationRuntime.Sync"/>。
        /// </summary>
        internal IReadOnlyList<PlcStationRuntime> Runtimes
        {
            get
            {
                lock (_workerLock)
                    return _workers.Select(w => w.Runtime).ToList();
            }
        }

        /// <summary>停旧扫车线程，按当前 JSON 新建并启动。</summary>
        private void RestartScan()
        {
            try { _scan?.Stop(); }
            catch { }

            var handshake = SignalConfigStore.Load<HandshakePointModel>(SignalConfigStore.Resolve(HandshakePointsPath));
            var release = SignalConfigStore.Load<ReleasePointModel>(SignalConfigStore.Resolve(ReleasePointsPath));
            _scan = new CarScanWorker(() => Runtimes, TickMs);
            _scan.ReplaceConfig(handshake, release);
            _scan.Start();
        }

        /// <summary>
        /// 先停全部旧连接，再按机构表为每个非空名称建 <see cref="PlcStationWorker"/>。
        /// 机构 Port&lt;=0 时回退进程级 <see cref="PlcPort"/>。
        /// </summary>
        private void RestartWorkers()
        {
            StopWorkers();
            var stationsFile = SignalConfigStore.Resolve(StationsPath);
            var stations = SignalConfigStore.Load<PlcStationModel>(stationsFile)
                .Where(s => !string.IsNullOrWhiteSpace(s.JgName))
                .ToList();

            var created = new List<PlcStationWorker>();
            lock (_workerLock)
            {
                foreach (var station in stations)
                {
                    var port = station.Port > 0 ? station.Port : PlcPort;
                    var runtime = new PlcStationRuntime(station);
                    var session = new SiemensPlcSession(PlcVersion, station.Ip, port, PlcSlot);
                    var worker = new PlcStationWorker(runtime, session, PlcPollMs, RefreshStatus);
                    _workers.Add(worker);
                    created.Add(worker);
                }
            }

            foreach (var worker in created)
                worker.Start();

            PushStatus();
        }

        /// <summary>清空列表后再 Stop，避免扫车在停连接过程中仍拿到旧 Runtime。</summary>
        private void StopWorkers()
        {
            List<PlcStationWorker> copy;
            lock (_workerLock)
            {
                copy = _workers.ToList();
                _workers.Clear();
            }

            foreach (var w in copy)
            {
                try { w.Stop(); }
                catch { }
            }
        }

        /// <summary>PLC Worker 每周期回调；约每 5 次才刷一次监视面板，避免 200ms 打满 UI。</summary>
        private void RefreshStatus()
        {
            if (Interlocked.Increment(ref _statusTick) % 5 != 0)
                return;
            PushStatus();
        }

        /// <summary>把各机构快照和扫车次数写进 MissionStatus，供 SimpleLite 进程监视。</summary>
        private void PushStatus()
        {
            List<PlcStationSnapshot> snaps;
            lock (_workerLock)
                snaps = _workers.Select(w => w.Runtime.ToSnapshot()).ToList();

            var st = (SignalInteractMissionStatus)status;
            st.Stations = snaps;
            st.ConnectedCount = snaps.Count(s => s.IsConnected);
            st.StationCount = snaps.Count;
            st.ScanTickCount = _scan?.TickCount ?? 0;
            if (_started)
                status.status = $"运行中 {st.ConnectedCount}/{st.StationCount} 已连接 扫车{_scan?.TickCount ?? 0}";
        }
    }
}
