using SimpleCore.Library;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Threading;

namespace StandardScene.Magnetic.Protocol
{
    /// <summary>
    /// FASS 2.0 PCB/UDP 共享监听枢纽。
    /// AGV 主动上报 100B 状态 → 解析并分发给对应车辆 → 回 0x10 时间戳应答到车辆 address:Port。
    /// 下发 0xB1/0xA1/0x01 等命令亦通过本枢纽单播到车辆 address:Port。
    /// </summary>
    internal static class Fass2UdpHub
    {
        private static readonly object SyncRoot = new object();
        private static readonly Dictionary<ushort, IFass2UdpCar> CarsByCode = new Dictionary<ushort, IFass2UdpCar>();

        private static UdpClient _listener;
        private static Thread _receiveThread;
        private static volatile bool _running;
        private static int _listenPort = 20103;
        private static readonly Dictionary<ushort, long> _lastUnknownCarLogUtcTicks = new Dictionary<ushort, long>();

        public static void Register(IFass2UdpCar car)
        {
            if (car == null)
            {
                return;
            }

            lock (SyncRoot)
            {
                CarsByCode[car.VehicleCode] = car;
            }
        }

        public static void Unregister(IFass2UdpCar car)
        {
            if (car == null)
            {
                return;
            }

            Unregister(car.VehicleCode, car);
        }

        public static void Unregister(ushort vehicleCode, IFass2UdpCar car)
        {
            if (car == null)
            {
                return;
            }

            lock (SyncRoot)
            {
                if (CarsByCode.TryGetValue(vehicleCode, out var existing) && ReferenceEquals(existing, car))
                {
                    CarsByCode.Remove(vehicleCode);
                }
            }
        }

        public static void EnsureStarted(int listenPort)
        {
            if (listenPort > 0)
            {
                _listenPort = listenPort;
            }

            lock (SyncRoot)
            {
                if (_running)
                {
                    return;
                }

                _listener = new UdpClient(_listenPort);
                _running = true;
                _receiveThread = new Thread(ReceiveLoop)
                {
                    IsBackground = true,
                    Name = $"Fass2UdpHub:{_listenPort}"
                };
                _receiveThread.Start();
                Diagnosis.Post($"Fass2UdpHub 已启动，监听 UDP {_listenPort}", "Fass2UdpHub", true);
            }
        }

        public static void Stop()
        {
            lock (SyncRoot)
            {
                _running = false;
                try
                {
                    _listener?.Close();
                }
                catch
                {
                }

                _listener = null;
            }

            try
            {
                _receiveThread?.Join(1000);
            }
            catch
            {
            }

            _receiveThread = null;
        }

        public static void SendToCar(IFass2UdpCar car, byte[] payload)
        {
            if (car == null || payload == null || payload.Length == 0)
            {
                return;
            }

            if (string.IsNullOrWhiteSpace(car.RemoteAddress) || car.RemotePort <= 0)
            {
                throw new InvalidOperationException(
                    $"Fass2UdpHub send failed: car VehicleCode={car.VehicleCode} remote endpoint not configured");
            }

            using var sender = new UdpClient();
            var remote = new IPEndPoint(IPAddress.Parse(car.RemoteAddress), car.RemotePort);
            sender.Send(payload, payload.Length, remote);
        }

        private static void ReceiveLoop()
        {
            while (_running)
            {
                try
                {
                    var remote = new IPEndPoint(IPAddress.Any, 0);
                    var packet = _listener.Receive(ref remote);
                    ProcessPacket(packet, remote);
                }
                catch (SocketException) when (!_running)
                {
                    break;
                }
                catch (ObjectDisposedException) when (!_running)
                {
                    break;
                }
                catch (Exception ex)
                {
                    Diagnosis.Post($"Fass2UdpHub receive error: {ex.Message}", "Fass2UdpHub", true);
                }
            }
        }

        private static void ProcessPacket(byte[] packet, IPEndPoint remote)
        {
            if (!Fass2Protocol.TryExtractStateFrame(packet, packet.Length, out var frame))
            {
                return;
            }

            Fass2StateReport report;
            try
            {
                report = Fass2Protocol.ParseState(frame);
            }
            catch (Exception ex)
            {
                Diagnosis.Post($"Fass2UdpHub parse error from {remote}: {ex.Message}", "Fass2UdpHub", true);
                return;
            }

            IFass2UdpCar car;
            lock (SyncRoot)
            {
                if (!CarsByCode.TryGetValue(report.Car, out car))
                {
                    LogUnknownCar(report.Car);
                    return;
                }
            }

            try
            {
                car.OnUdpStateReceived(report);
            }
            catch (Exception ex)
            {
                Diagnosis.Post($"Fass2UdpHub dispatch error car={report.Car}: {ex.Message}", "Fass2UdpHub", true);
            }

            try
            {
                var ack = Fass2Protocol.BuildStateResponse(report.Car, Fass2Protocol.BeijingUnixTimeMs());
                SendToCar(car, ack);
            }
            catch (Exception ex)
            {
                Diagnosis.Post($"Fass2UdpHub 0x10 reply failed car={report.Car}: {ex.Message}", "Fass2UdpHub", true);
            }
        }

        private static void LogUnknownCar(ushort carCode)
        {
            var nowTicks = DateTime.UtcNow.Ticks;
            lock (SyncRoot)
            {
                if (_lastUnknownCarLogUtcTicks.TryGetValue(carCode, out var lastTicks)
                    && nowTicks - lastTicks < TimeSpan.FromSeconds(5).Ticks)
                {
                    return;
                }

                _lastUnknownCarLogUtcTicks[carCode] = nowTicks;
            }

            var registered = string.Join(",", CarsByCode.Keys);
            if (string.IsNullOrEmpty(registered))
            {
                registered = "(无)";
            }

            Diagnosis.Post(
                $"Fass2UdpHub 收到 Car={carCode} 的状态，但未注册该编号（已注册: {registered}）。请检查 Mag2Car.VehicleCode 与模拟器车号是否一致，并重新启动场景。",
                "Fass2UdpHub",
                true);
        }
    }
}
