using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;

namespace StandardScene.Fass2Simulator
{
    public sealed class Fass2SimUdpHost : IDisposable
    {
        private readonly Fass2SimConfig _config;
        private readonly Fass2SimVehicle _vehicle;
        private readonly Fass2SimCommandHandler _commandHandler;
        private readonly Fass2SimMotionEngine _motionEngine;
        private readonly Fass2SimActionEngine _actionEngine;
        private readonly IPEndPoint _schedulerEndpoint;

        private UdpClient _listener;
        private UdpClient _sender;
        private Thread _receiveThread;
        private Timer _reportTimer;
        private volatile bool _running;

        public Fass2SimUdpHost(
            Fass2SimConfig config,
            Fass2SimVehicle vehicle,
            Fass2SimCommandHandler commandHandler,
            Fass2SimMotionEngine motionEngine,
            Fass2SimActionEngine actionEngine)
        {
            _config = config;
            _vehicle = vehicle;
            _commandHandler = commandHandler;
            _motionEngine = motionEngine;
            _actionEngine = actionEngine;
            _schedulerEndpoint = new IPEndPoint(IPAddress.Parse(config.SchedulerHost), config.SchedulerListenPort);
        }

        public bool IsRunning => _running;

        public event EventHandler StatusReported;

        public void Start()
        {
            if (_running)
            {
                return;
            }

            var listenAddress = IPAddress.Parse(_config.VehicleListenAddress);
            _listener = new UdpClient(new IPEndPoint(listenAddress, _config.VehicleListenPort));
            _sender = new UdpClient();
            _running = true;

            _receiveThread = new Thread(ReceiveLoop)
            {
                IsBackground = true,
                Name = $"Fass2SimRx:{_config.VehicleListenPort}"
            };
            _receiveThread.Start();

            var interval = Math.Max(50, _config.ReportIntervalMs);
            _reportTimer = new Timer(SendStateReport, null, 0, interval);

            Fass2SimLog.WriteLine($"[{Now()}] 模拟车已启动");
            Fass2SimLog.WriteLine($"  车体监听 : {_config.VehicleListenAddress}:{_config.VehicleListenPort}");
            Fass2SimLog.WriteLine($"  上报目标 : {_config.SchedulerHost}:{_config.SchedulerListenPort}");
            Fass2SimLog.WriteLine($"  车号     : {_config.VehicleCode}");
            Fass2SimLog.WriteLine($"  上报周期 : {interval} ms");
            Fass2SimLog.WriteLine($"  路段模拟 : dist={_config.DefaultSegmentDistance}mm 或 node.Distance, speed={_config.DefaultSpeed}mm/s");
            Fass2SimLog.WriteLine($"  动作延时 : {_config.ActionDelayMs}ms（0xA1 到位），AutoComplete={_config.AutoCompleteStationActions}");
            Fass2SimLog.WriteLine($"  初始节点 : {_config.InitialNode}, 状态={Fass2SimProtocol.StateText(_config.InitialState)}");
            Fass2SimLog.WriteLine("等待 Mag2Car 联调（address=127.0.0.1, Port=5000, ListenPort=20103, VehicleCode=1）");
        }

        public void Stop()
        {
            _running = false;
            _reportTimer?.Dispose();
            _reportTimer = null;

            try
            {
                _listener?.Close();
            }
            catch
            {
            }

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

            _receiveThread = null;
            _listener = null;

            try
            {
                _sender?.Close();
            }
            catch
            {
            }

            _sender = null;
            Fass2SimLog.WriteLine($"[{Now()}] 模拟车已停止");
        }

        public void Dispose()
        {
            Stop();
        }

        private void ReceiveLoop()
        {
            while (_running)
            {
                try
                {
                    var remote = new IPEndPoint(IPAddress.Any, 0);
                    var packet = _listener.Receive(ref remote);
                    _commandHandler.Handle(packet, remote.ToString());
                }
                catch (SocketException) when (!_running)
                {
                    break;
                }
                catch (ObjectDisposedException) when (!_running)
                {
                    break;
                }
                catch (Exception ex)
                {
                    Fass2SimLog.WriteLine($"[{Now()}] 接收异常: {ex.Message}");
                }
            }
        }

        private void SendStateReport(object _)
        {
            if (!_running)
            {
                return;
            }

            try
            {
                var interval = Math.Max(50, _config.ReportIntervalMs);
                _actionEngine.Tick(interval);
                _motionEngine.Tick(interval);

                var snapshot = _vehicle.Snapshot();
                var frame = Fass2SimProtocol.BuildState(snapshot);
                _sender.Send(frame, frame.Length, _schedulerEndpoint);
                _vehicle.RecordStateReportSent();
                StatusReported?.Invoke(this, EventArgs.Empty);

                if (snapshot.StateReportsSent == 0 || snapshot.StateReportsSent % 25 == 0)
                {
                    var pending = _actionEngine.ExpectedStation == null
                        ? "-"
                        : Fass2SimActionResolver.DescribePending(_actionEngine.ExpectedStation, snapshot);
                    if (string.IsNullOrEmpty(pending))
                    {
                        pending = "-";
                    }

                    Fass2SimLog.WriteLine(
                        $"[{Now()}] TX 状态 #{snapshot.StateReportsSent + 1}: node={snapshot.Node}, dist={snapshot.Distance}, state={Fass2SimProtocol.StateText(snapshot.State)}, lift={snapshot.Lift}, pending={pending}, task={snapshot.Task}");
                }
            }
            catch (Exception ex)
            {
                Fass2SimLog.WriteLine($"[{Now()}] 上报异常: {ex.Message}");
            }
        }

        private static string Now()
        {
            return DateTime.Now.ToString("HH:mm:ss.fff");
        }
    }
}
