using System;
using System.Collections.Generic;
using System.Linq;
using SimpleCore.Library;
using SimpleLite.RCS.CarTypes;
using StandardScene.Signal.Model;
using StandardScene.Signal.Plc;

namespace StandardScene.Signal.Logic
{
    /// <summary>
    /// 磁条交管运行态：按管控区名称分组排队；放行按「本线 + 冲突对向线」站点并集查当前占用。
    /// 不维护 pending 在途占额。
    /// </summary>
    public sealed class MagControlRuntime
    {
        private readonly object _lock = new object();
        private Dictionary<int, MagControlAreaModel> _byTrigger = new Dictionary<int, MagControlAreaModel>();
        private readonly Dictionary<int, DateTime> _releaseLog = new Dictionary<int, DateTime>();
        private readonly Dictionary<int, Arrival> _arrivals = new Dictionary<int, Arrival>();

        public int RuleCount
        {
            get
            {
                lock (_lock)
                    return _byTrigger.Count;
            }
        }

        /// <summary>已不再使用 pending；保留属性避免旧 UI 绑定报错，恒为 0。</summary>
        public int PendingCount => 0;

        public void Replace(IEnumerable<MagControlAreaModel> rows)
        {
            var map = (rows ?? Array.Empty<MagControlAreaModel>())
                .Where(x => x != null && x.TriggerSit > 0 && x.IsUse)
                .GroupBy(x => x.TriggerSit)
                .ToDictionary(g => g.Key, g => g.First());
            lock (_lock)
            {
                _byTrigger = map;
                _arrivals.Clear();
            }
        }

        /// <summary>
        /// 同名区排队；放行前查本线 + 冲突对向线站点并集上的当前占用。
        /// 另计：已放行仍停在冲突触发点上的运行中车辆（避免 site 未进区时空窗连放）。
        /// </summary>
        public (int Waiting, int Released) ScanAndRelease()
        {
            List<MagControlAreaModel> rules;
            lock (_lock)
                rules = _byTrigger.Values.ToList();

            if (rules.Count == 0)
                return (0, 0);

            var snapshots = SnapshotCars();
            var occupancies = snapshots
                .Select(s => (s.CarId, s.OccupiedSiteIds))
                .ToList();

            var groups = rules
                .GroupBy(AreaKey, StringComparer.OrdinalIgnoreCase)
                .Select(g => BuildGroup(g.Key, g.ToList()))
                .ToList();

            lock (_lock)
                SweepArrivals(snapshots);

            var waiting = 0;
            var released = 0;
            foreach (var group in groups)
            {
                var waiters = CollectWaiters(group, snapshots);
                waiting += waiters.Count;
                if (waiters.Count == 0)
                    continue;

                // 停稳排队的车不计入区内占用；本拍已放行的车单独记冲突占额
                var exclude = new HashSet<int>(waiters.Select(w => w.CarId));
                var releasedThisScan = new List<Waiter>();

                waiters.Sort((a, b) => CompareWaiters(a, b, group.WaitSeconds));
                foreach (var waiter in waiters)
                {
                    var checkSites = BuildConflictSites(group, waiter);
                    var capacity = MagControlAreaGate.NormalizeCapacity(waiter.Capacity);
                    var others = CountConflictingOccupancy(
                        group, waiter, checkSites, occupancies, snapshots, exclude, releasedThisScan);
                    if (others >= capacity)
                        continue;

                    SignalCarAdapter.StopCharge(waiter.Car);
                    SignalCarAdapter.StartMagCar(waiter.Car);
                    released++;
                    exclude.Add(waiter.CarId);
                    releasedThisScan.Add(waiter);
                    NoteRelease(waiter, group, others, capacity, checkSites);
                }
            }

            return (waiting, released);
        }

        /// <summary>
        /// 冲突占用 = 并集站点上的当前车
        /// + 本区冲突触发点上仍「运行中」的车（刚放行、site 尚未进 ControlArea）
        /// + 本拍已放行且与本线冲突的车。
        /// </summary>
        private static int CountConflictingOccupancy(
            AreaGroup group,
            Waiter waiter,
            IReadOnlyList<int> checkSites,
            List<(int CarId, IReadOnlyList<int> OccupiedSiteIds)> occupancies,
            List<CarSnapshot> snapshots,
            HashSet<int> exclude,
            List<Waiter> releasedThisScan)
        {
            var others = MagControlAreaGate.CountOccupyingCars(occupancies, checkSites, exclude);
            var counted = new HashSet<int>();

            foreach (var snap in snapshots)
            {
                if (snap == null || exclude.Contains(snap.CarId) || counted.Contains(snap.CarId))
                    continue;
                if (!snap.IsMagCar)
                    continue;

                // 已在并集站点上：CountOccupyingCars 已计，这里补「运行中仍在冲突触发点」
                if (snap.IsStopped)
                    continue;
                if (snap.Sit <= 0 || !group.Triggers.Contains(snap.Sit))
                    continue;
                if (!group.RouteByTrigger.TryGetValue(snap.Sit, out var route))
                    continue;
                if (!Conflicts(waiter.CompatGroup, waiter.AreaSites, route.CompatGroup, route.AreaSites))
                    continue;

                // 若 site 已在 checkSites 里，上面 others 可能已计（触发点一般不在列表）
                if (MagControlAreaGate.OccupiesListedSites(snap.OccupiedSiteIds, checkSites))
                    continue;

                counted.Add(snap.CarId);
                others++;
            }

            foreach (var releasedCar in releasedThisScan)
            {
                if (releasedCar == null || counted.Contains(releasedCar.CarId))
                    continue;
                if (!Conflicts(
                        waiter.CompatGroup, waiter.AreaSites,
                        releasedCar.CompatGroup, releasedCar.AreaSites))
                    continue;
                counted.Add(releasedCar.CarId);
                others++;
            }

            return others;
        }

        /// <summary>单车判定：本线 + 同名区内冲突对向线站点并集的当前占用。</summary>
        public MagControlReleaseDecision Decide(int carId, int triggerSit)
        {
            MagControlAreaModel rule;
            List<MagControlAreaModel> sameArea;
            lock (_lock)
            {
                if (triggerSit <= 0 || !_byTrigger.TryGetValue(triggerSit, out rule) || rule == null)
                {
                    return new MagControlReleaseDecision
                    {
                        RuleFound = false,
                        Reason = "no mag-control rule"
                    };
                }

                var key = AreaKey(rule);
                sameArea = _byTrigger.Values
                    .Where(r => string.Equals(AreaKey(r), key, StringComparison.OrdinalIgnoreCase))
                    .ToList();
            }

            var group = BuildGroup(AreaKey(rule), sameArea);
            if (!group.RouteByTrigger.TryGetValue(triggerSit, out var route))
            {
                return new MagControlReleaseDecision
                {
                    RuleFound = false,
                    Reason = "no mag-control route"
                };
            }

            var waiter = new Waiter
            {
                CarId = carId,
                TriggerSit = triggerSit,
                CompatGroup = route.CompatGroup,
                AreaSites = route.AreaSites,
                Capacity = route.Capacity
            };
            var checkSites = BuildConflictSites(group, waiter);
            var occupancies = new List<(int CarId, IReadOnlyList<int> OccupiedSiteIds)>();
            foreach (var car in SignalCarAdapter.All())
            {
                if (car == null)
                    continue;
                occupancies.Add((car.id, MagControlAreaGate.CollectOccupiedSiteIds(car)));
            }

            return MagControlAreaGate.Evaluate(
                carId,
                triggerSit,
                checkSites,
                rule.ControlCapacity,
                rule.ControlReleaseStartStop,
                occupancies);
        }

        internal static string AreaKey(MagControlAreaModel rule)
        {
            if (rule == null)
                return "#sit:0";
            var name = (rule.AreaName ?? string.Empty).Trim();
            return name.Length > 0 ? name : "#sit:" + rule.TriggerSit;
        }

        internal static int CompareWaiters(Waiter a, Waiter b, int waitSeconds)
        {
            if (waitSeconds > 0)
            {
                if (a.Priority < b.Priority &&
                    (b.ArrivedAt - a.ArrivedAt).TotalSeconds > waitSeconds)
                    return -1;
                if (b.Priority < a.Priority &&
                    (a.ArrivedAt - b.ArrivedAt).TotalSeconds > waitSeconds)
                    return 1;
            }

            var byPriority = b.Priority.CompareTo(a.Priority);
            if (byPriority != 0)
                return byPriority;
            var byArrival = a.ArrivedAt.CompareTo(b.ArrivedAt);
            if (byArrival != 0)
                return byArrival;
            return a.CarId.CompareTo(b.CarId);
        }

        internal static bool Conflicts(
            string compatA,
            IReadOnlyList<int> sitesA,
            string compatB,
            IReadOnlyList<int> sitesB)
        {
            if (string.Equals(compatA ?? "", compatB ?? "", StringComparison.OrdinalIgnoreCase))
                return true;
            return MagControlAreaGate.SitesIntersect(sitesA, sitesB);
        }

        /// <summary>
        /// 本线站点 + 同名区内所有与本线冲突的对向/交叉线路站点并集。
        /// 例如西环 NS-W 会并入东/北/南列表，100/101/104 有车时都能挡住触发点 4。
        /// </summary>
        private static IReadOnlyList<int> BuildConflictSites(AreaGroup group, Waiter waiter)
        {
            var set = new HashSet<int>();
            if (waiter?.AreaSites != null)
            {
                foreach (var site in waiter.AreaSites)
                {
                    if (site > 0)
                        set.Add(site);
                }
            }

            if (group?.RouteByTrigger == null)
                return set.OrderBy(x => x).ToList();

            foreach (var route in group.RouteByTrigger.Values)
            {
                if (route?.AreaSites == null)
                    continue;
                if (!Conflicts(waiter.CompatGroup, waiter.AreaSites, route.CompatGroup, route.AreaSites))
                    continue;
                foreach (var site in route.AreaSites)
                {
                    if (site > 0)
                        set.Add(site);
                }
            }

            return set.OrderBy(x => x).ToList();
        }

        private static AreaGroup BuildGroup(string key, List<MagControlAreaModel> rows)
        {
            var ordered = rows.OrderBy(r => r.TriggerSit).ToList();
            var triggers = new HashSet<int>(ordered.Select(r => r.TriggerSit));
            var waitSeconds = 0;
            var routeByTrigger = new Dictionary<int, RouteInfo>();
            foreach (var row in ordered)
            {
                waitSeconds = Math.Max(waitSeconds, row.WaitSeconds);
                var sites = MagControlAreaGate.ParseSiteIds(row.ControlArea, row.TriggerSit);
                routeByTrigger[row.TriggerSit] = new RouteInfo
                {
                    CompatGroup = MagControlAreaGate.NormalizeCompatGroup(row.CompatGroup),
                    Capacity = MagControlAreaGate.NormalizeCapacity(row.ControlCapacity),
                    AreaSites = sites,
                    Priority = row.Priority
                };
            }

            return new AreaGroup
            {
                Key = key,
                WaitSeconds = waitSeconds,
                Triggers = triggers,
                RouteByTrigger = routeByTrigger
            };
        }

        private List<CarSnapshot> SnapshotCars()
        {
            var list = new List<CarSnapshot>();
            foreach (var car in SignalCarAdapter.All())
            {
                if (car == null)
                    continue;
                list.Add(new CarSnapshot
                {
                    Car = car,
                    CarId = car.id,
                    OccupiedSiteIds = MagControlAreaGate.CollectOccupiedSiteIds(car),
                    IsMagCar = SignalCarAdapter.IsMagCar(car),
                    IsStopped = SignalCarAdapter.IsStopped(car),
                    Sit = SignalCarAdapter.PositionId(car)
                });
            }

            return list;
        }

        private List<Waiter> CollectWaiters(AreaGroup group, List<CarSnapshot> snapshots)
        {
            var waiters = new List<Waiter>();
            var now = DateTime.Now;
            foreach (var snap in snapshots)
            {
                if (!snap.IsMagCar || !snap.IsStopped)
                    continue;
                if (snap.Sit <= 0 || !group.Triggers.Contains(snap.Sit))
                    continue;

                DateTime arrivedAt;
                lock (_lock)
                {
                    if (!_arrivals.TryGetValue(snap.CarId, out var arrival) || arrival.TriggerSit != snap.Sit)
                    {
                        arrival = new Arrival { TriggerSit = snap.Sit, ArrivedAt = now };
                        _arrivals[snap.CarId] = arrival;
                    }

                    arrivedAt = arrival.ArrivedAt;
                }

                if (!group.RouteByTrigger.TryGetValue(snap.Sit, out var route))
                    continue;

                waiters.Add(new Waiter
                {
                    Car = snap.Car,
                    CarId = snap.CarId,
                    TriggerSit = snap.Sit,
                    AreaKey = group.Key,
                    CompatGroup = route.CompatGroup,
                    Capacity = route.Capacity,
                    AreaSites = route.AreaSites,
                    Priority = route.Priority,
                    ArrivedAt = arrivedAt
                });
            }

            return waiters;
        }

        private void SweepArrivals(List<CarSnapshot> snapshots)
        {
            var present = new HashSet<int>();
            foreach (var snap in snapshots)
            {
                if (!snap.IsMagCar || !snap.IsStopped || snap.Sit <= 0)
                    continue;
                if (_arrivals.TryGetValue(snap.CarId, out var arrival) && arrival.TriggerSit == snap.Sit)
                    present.Add(snap.CarId);
            }

            var stale = _arrivals.Keys.Where(id => !present.Contains(id)).ToList();
            foreach (var id in stale)
                _arrivals.Remove(id);
        }

        private void NoteRelease(
            Waiter waiter,
            AreaGroup group,
            int others,
            int capacity,
            IReadOnlyList<int> checkSites)
        {
            var groupName = string.IsNullOrEmpty(waiter.CompatGroup) ? "-" : waiter.CompatGroup;
            var carName = waiter.Car?.name;
            var namePart = string.IsNullOrWhiteSpace(carName) ? "" : $" name={carName}";
            var sites = waiter.AreaSites == null || waiter.AreaSites.Count == 0
                ? "-"
                : string.Join(",", waiter.AreaSites);
            var conflict = checkSites == null || checkSites.Count == 0
                ? "-"
                : string.Join(",", checkSites);
            var message =
                $"放行 MagCar={waiter.CarId}{namePart} 区={group.Key} 组={groupName} 触发点={waiter.TriggerSit} " +
                $"本线={sites} 冲突并集={conflict} 当前占用={others}/{capacity}";

            MagTrafficFileLogger.Write(carName, waiter.CarId, message);

            if (_releaseLog.TryGetValue(waiter.CarId, out var last) && (DateTime.Now - last).TotalSeconds < 5)
                return;
            _releaseLog[waiter.CarId] = DateTime.Now;
            Diagnosis.Log($"磁条交管{message}", "MagTraffic", true);
        }

        internal sealed class Waiter
        {
            public Car Car;
            public int CarId;
            public int TriggerSit;
            public string AreaKey;
            public string CompatGroup;
            public int Capacity;
            public IReadOnlyList<int> AreaSites;
            public int Priority;
            public DateTime ArrivedAt;
        }

        private sealed class Arrival
        {
            public int TriggerSit;
            public DateTime ArrivedAt;
        }

        private sealed class RouteInfo
        {
            public string CompatGroup;
            public int Capacity;
            public IReadOnlyList<int> AreaSites;
            public int Priority;
        }

        private sealed class AreaGroup
        {
            public string Key;
            public int WaitSeconds;
            public HashSet<int> Triggers;
            public Dictionary<int, RouteInfo> RouteByTrigger;
        }

        private sealed class CarSnapshot
        {
            public Car Car;
            public int CarId;
            public IReadOnlyList<int> OccupiedSiteIds;
            public bool IsMagCar;
            public bool IsStopped;
            public int Sit;
        }
    }
}
