using SimpleCore;
using SimpleCore.Compiler;
using SimpleCore.PropType;
using SimpleCore.Traffic;
using System;
using System.Collections.Generic;
using System.Linq;

namespace StandardScene.Magnetic.Tasking
{
    /// <summary>
    /// 方案 B′：用任务下发的 siteIds 经 SegmentPlan.Forecast 建立 seqScope/pendingLocks，再段级 TryLock/Leave。
    /// </summary>
    internal sealed class Fass2TrafficLocker
    {
        private readonly AbstractCar _car;
        private readonly Action<string> _log;

        public Fass2TrafficLocker(AbstractCar car, Action<string> log)
        {
            _car = car ?? throw new ArgumentNullException(nameof(car));
            _log = log ?? (_ => { });
        }

        public void PrepareSequence(IReadOnlyList<int> siteIds, int fromIndex)
        {
            if (siteIds == null || siteIds.Count == 0)
            {
                throw new InvalidOperationException("traffic plan siteIds is empty");
            }

            fromIndex = Math.Max(0, Math.Min(fromIndex, siteIds.Count - 1));
            var startSiteId = siteIds[fromIndex];
            var startSite = SimpleLib.GetSite(startSiteId);
            if (startSite == null)
            {
                throw new InvalidOperationException($"traffic start site invalid: {startSiteId}");
            }

            // TrafficReset 会 programs.Clear() 把 now 置空；TryLock 在真正占点前要求 programs.now != null，
            // 否则抛 Program obsoleted。环线拦截路径仍处在 move 脚本的 actualSendScript 中，需保留/重建锚点。
            var keepProgram = _car.status.programs.now;

            _log($"prepare reset start={startSiteId}, goal={siteIds[siteIds.Count - 1]}, fromIndex={fromIndex}, route=[{string.Join(",", siteIds)}]");
            try
            {
                _car.TrafficReset(startSite, makeAvailable: true, strict: false);
            }
            catch (Exception ex) when (IsStartOccupiedByOtherCar(ex))
            {
                // 起点被他车占用时不能 Reset；若本车已持有该站则跳过 Reset 继续建序，否则交给上层等待。
                if (Array.IndexOf(_car.status.holdingLocks, startSiteId) < 0)
                {
                    throw new InvalidOperationException(
                        $"traffic start site {startSiteId} occupied by another car", ex);
                }

                _log($"prepare reset skipped, already holding start={startSiteId}, ex={ex.Message}");
            }

            EnsureProgramAnchor(keepProgram);

            if (fromIndex >= siteIds.Count - 1)
            {
                LogTrafficState("prepare single-site");
                return;
            }

            Fass2RouteHelper.ForecastTrafficSequence(_car, siteIds, fromIndex);
            LogTrafficState("prepare forecast ok");
        }

        public void RebaseFrom(IReadOnlyList<int> siteIds, int fromIndex, int windowSize)
        {
            PrepareSequence(siteIds, fromIndex);
            EnsureWindowLocked(siteIds, fromIndex, Math.Max(1, windowSize));
        }

        private static bool IsStartOccupiedByOtherCar(Exception ex)
        {
            for (var cur = ex; cur != null; cur = cur.InnerException)
            {
                var msg = cur.Message ?? string.Empty;
                if (msg.IndexOf("already locks it", StringComparison.OrdinalIgnoreCase) >= 0 ||
                    msg.IndexOf("unavailable site", StringComparison.OrdinalIgnoreCase) >= 0)
                {
                    return true;
                }
            }

            return false;
        }

        public bool TryLockNext(int siteId)
        {
            if (!_car.status.usage.Get().scheduling)
            {
                throw new InvalidOperationException("abandoned");
            }

            if (Array.IndexOf(_car.status.holdingLocks, siteId) >= 0)
            {
                return true;
            }

            if (_car.status.pendingLocks.Length > 0 && _car.status.pendingLocks[0] != siteId)
            {
                // 窗口重入时可能再次请求已锁过的更早站点；交给 EnsureWindowLocked 跳过，避免 UDP 线程抛异常。
                _log($"lock skip site={siteId}, expect pending0={_car.status.pendingLocks[0]}, holding=[{FormatInts(_car.status.holdingLocks)}]");
                return false;
            }

            EnsureProgramAnchor(keepProgram: null);

            if (TrafficControl.TryLock(_car, siteId))
            {
                _log($"lock ok site={siteId}, holding=[{FormatInts(_car.status.holdingLocks)}]");
                return true;
            }

            _log($"lock wait site={siteId}, blockedBy={FormatBlocked()}, TCStat={FormatTcStat()}");
            return false;
        }

        public void LeavePassed(int siteId)
        {
            if (_car.status.holdingLocks.Length <= 1)
            {
                _log($"leave skip site={siteId}, holding count={_car.status.holdingLocks.Length}");
                return;
            }

            if (Array.IndexOf(_car.status.holdingLocks, siteId) < 0)
            {
                _log($"leave skip site={siteId}, not in holding");
                return;
            }

            EnsureProgramAnchor(keepProgram: null);
            TrafficControl.Leave(_car, siteId);
            _log($"leave site={siteId}, holding=[{FormatInts(_car.status.holdingLocks)}]");
        }

        /// <summary>
        /// 释放路径上严格位于 keepFromIndex 之前的持锁站点（当前站及前方窗口保留）。
        /// </summary>
        public void ReleaseLocksBehind(IReadOnlyList<int> siteIds, int keepFromIndex)
        {
            if (siteIds == null || siteIds.Count == 0 || _car.status.holdingLocks.Length <= 1)
            {
                return;
            }

            keepFromIndex = Math.Max(0, Math.Min(keepFromIndex, siteIds.Count - 1));
            var holdingSnapshot = _car.status.holdingLocks.ToArray();
            foreach (var siteId in holdingSnapshot)
            {
                if (_car.status.holdingLocks.Length <= 1)
                {
                    break;
                }

                var index = IndexOfSite(siteIds, siteId);
                if (index < 0 || index >= keepFromIndex)
                {
                    continue;
                }

                LeavePassed(siteId);
            }
        }

        private static int IndexOfSite(IReadOnlyList<int> siteIds, int siteId)
        {
            for (var i = 0; i < siteIds.Count; i++)
            {
                if (siteIds[i] == siteId)
                {
                    return i;
                }
            }

            return -1;
        }

        public int EnsureWindowLocked(IReadOnlyList<int> siteIds, int fromIndex, int windowSize)
        {
            if (siteIds == null || siteIds.Count == 0)
            {
                return 0;
            }

            fromIndex = Math.Max(0, Math.Min(fromIndex, siteIds.Count - 1));
            windowSize = Math.Max(1, Math.Min(windowSize, 10));
            var maxIndex = Math.Min(siteIds.Count - 1, fromIndex + windowSize - 1);

            // 返回值必须是从 fromIndex 起的连续已占/新锁站数，供 0xB1 窗口切片使用。
            // UDP Tick 会反复进入：已在 holding 的站直接计数，禁止再次对已消费的 pending 站 TryLock。
            var lockedCount = 0;
            for (var i = fromIndex; i <= maxIndex; i++)
            {
                var siteId = siteIds[i];
                if (Array.IndexOf(_car.status.holdingLocks, siteId) >= 0)
                {
                    lockedCount++;
                    continue;
                }

                if (_car.status.pendingLocks.Length == 0 || _car.status.pendingLocks[0] != siteId)
                {
                    _log(
                        $"window gap site={siteId}, pending0={(_car.status.pendingLocks.Length > 0 ? _car.status.pendingLocks[0].ToString() : "-")}, holding=[{FormatInts(_car.status.holdingLocks)}]");
                    break;
                }

                if (!TryLockNext(siteId))
                {
                    break;
                }

                lockedCount++;
            }

            if (lockedCount == 0)
            {
                lockedCount = 1;
                _log($"window warn: no holding overlap index={fromIndex}, site={siteIds[fromIndex]}, holding=[{FormatInts(_car.status.holdingLocks)}]");
            }

            _log($"window locked={lockedCount}/{windowSize}, fromIndex={fromIndex}, holding=[{FormatInts(_car.status.holdingLocks)}], pending=[{FormatInts(_car.status.pendingLocks)}]");
            return lockedCount;
        }

        public void FinalizeAtSite(int siteId)
        {
            _log($"finalize site={siteId}, holding=[{FormatInts(_car.status.holdingLocks)}], pending=[{FormatInts(_car.status.pendingLocks)}]");
        }

        /// <summary>
        /// SimpleCore.TryLock 在写锁前检查 programs.now；为空则抛 Program obsoleted。
        /// </summary>
        private void EnsureProgramAnchor(CarProgram keepProgram)
        {
            if (_car.status.programs.now != null)
            {
                return;
            }

            if (keepProgram != null)
            {
                _car.status.programs.now = keepProgram;
                _log($"restore programs.now after TrafficReset, name={keepProgram.name}, state={keepProgram.status.state}");
                return;
            }

            var dummy = new CarProgram
            {
                name = $"fass2-traffic-anchor:{_car.id}",
                plans = new[]
                {
                    new SegmentPlan { usingCar = _car }
                }
            };
            dummy.status.state = CarProgram.StatusEnum.Dummy;
            _car.status.programs.now = dummy;
            _log($"install dummy programs.now for TryLock, name={dummy.name}");
        }

        private void LogTrafficState(string prefix)
        {
            _log(
                $"{prefix}, holding=[{FormatInts(_car.status.holdingLocks)}], pending=[{FormatInts(_car.status.pendingLocks)}], seqScope=[{FormatInts(_car.status.seqScope)}], seqPtr={_car.status.seqPtr}");
        }

        private string FormatBlocked()
        {
            if (_car.status.blockedBy == null || _car.status.blockedBy.Length == 0)
            {
                return "-";
            }

            return string.Join(",", _car.status.blockedBy.Select(p => $"{p.Item1}:{p.Item2}"));
        }

        private string FormatTcStat()
        {
            try
            {
                var tc = _car.status.TCStat;
                return string.IsNullOrWhiteSpace(tc) ? "-" : tc;
            }
            catch
            {
                return "-";
            }
        }

        private static string FormatInts(int[] values)
        {
            return values == null || values.Length == 0 ? string.Empty : string.Join(",", values);
        }
    }
}
