using System;
using System.Diagnostics;
using MultiWheelC.Control.Abstractions;
using MultiWheelC.Control.Allocation;
using MultiWheelC.Trajectory;
using MyParking.Shared;

namespace MultiWheelC.Control.Execution
{
    // 表示公共轨迹跟踪核心单周期的计算结果，不包含底盘发送结果。
    public enum PathTrackingCycleResult
    {
        Inactive = 0,
        CommandGenerated = 1,
        Completed = 2,
        Faulted = 3
    }

    // 保存公共核心生成的GCP命令、轨迹投影和分阶段计算耗时。
    public readonly struct PathTrackingCycleOutput
    {
        public PathTrackingCycleOutput(
            PathTrackingCycleResult result,
            GcpMotionCommand? command,
            TrajectoryProjection? projection,
            double projectionMilliseconds,
            double controllerComputeMilliseconds)
        {
            Result = result;
            Command = command;
            Projection = projection;
            ProjectionMilliseconds = projectionMilliseconds;
            ControllerComputeMilliseconds =
                controllerComputeMilliseconds;
        }

        public PathTrackingCycleResult Result { get; }

        public GcpMotionCommand? Command { get; }

        public TrajectoryProjection? Projection { get; }

        public double ProjectionMilliseconds { get; }

        public double ControllerComputeMilliseconds { get; }
    }

    // 统一处理单车和虚拟车队共有的轨迹投影、速度整形及GCP命令生成。
    public sealed class PathTrackingCore
    {
        private const double ZeroReferenceSpeedToleranceMetersPerSecond =
            1e-6;
        private const double StartupRegionMeters = 0.02;
        private const double StartupPreviewDistanceMeters = 0.05;
        private const double MaximumStartupSpeedMetersPerSecond = 0.08;
        private const double ProjectionBackwardSearchDistanceMeters =
            0.10;
        private const double ProjectionForwardSearchDistanceMeters =
            1.00;

        private readonly ILateralController _lateralController;
        private readonly ILongitudinalController _longitudinalController;
        private readonly GcpCommandAllocator _gcpAllocator;
        private readonly double _motionDirectionInBodyRadians;

        private Trajectory2D _trajectory;
        private double _terminalTravelDirection = 1.0;

        public PathTrackingCore(
            ILateralController lateralController,
            ILongitudinalController longitudinalController,
            GcpCommandAllocator gcpAllocator,
            double finishDistanceMeters = 0.04,
            double finishSpeedMetersPerSecond = 0.02,
            double finishHeadingToleranceRadians =
                3.0 * Math.PI / 180.0,
            double maximumDistanceToTrajectoryMeters = 0.30,
            double terminalBrakingPreviewMeters = 0.02,
            double terminalApproachDistanceMeters = 0.10,
            double terminalApproachGainPerSecond = 0.8,
            double maximumTerminalApproachSpeedMetersPerSecond = 0.05,
            double curvaturePreviewSeconds = 0.20,
            double maximumCurvaturePreviewMeters = 0.12,
            double motionDirectionInBodyRadians = 0.0)
        {
            _lateralController = lateralController ??
                throw new ArgumentNullException(
                    nameof(lateralController));
            _longitudinalController = longitudinalController ??
                throw new ArgumentNullException(
                    nameof(longitudinalController));
            _gcpAllocator = gcpAllocator ??
                throw new ArgumentNullException(
                    nameof(gcpAllocator));

            NumericGuard.EnsureFinitePositive(
                finishDistanceMeters,
                nameof(finishDistanceMeters));
            NumericGuard.EnsureFiniteNonNegative(
                finishSpeedMetersPerSecond,
                nameof(finishSpeedMetersPerSecond));
            NumericGuard.EnsureFinitePositive(
                finishHeadingToleranceRadians,
                nameof(finishHeadingToleranceRadians));
            NumericGuard.EnsureFinitePositive(
                maximumDistanceToTrajectoryMeters,
                nameof(maximumDistanceToTrajectoryMeters));
            NumericGuard.EnsureFiniteNonNegative(
                terminalBrakingPreviewMeters,
                nameof(terminalBrakingPreviewMeters));
            NumericGuard.EnsureFinitePositive(
                terminalApproachDistanceMeters,
                nameof(terminalApproachDistanceMeters));
            NumericGuard.EnsureFinitePositive(
                terminalApproachGainPerSecond,
                nameof(terminalApproachGainPerSecond));
            NumericGuard.EnsureFinitePositive(
                maximumTerminalApproachSpeedMetersPerSecond,
                nameof(maximumTerminalApproachSpeedMetersPerSecond));
            NumericGuard.EnsureFiniteNonNegative(
                curvaturePreviewSeconds,
                nameof(curvaturePreviewSeconds));
            NumericGuard.EnsureFiniteNonNegative(
                maximumCurvaturePreviewMeters,
                nameof(maximumCurvaturePreviewMeters));
            NumericGuard.EnsureFinite(
                motionDirectionInBodyRadians,
                nameof(motionDirectionInBodyRadians));

            if (terminalApproachDistanceMeters <=
                finishDistanceMeters)
            {
                throw new ArgumentOutOfRangeException(
                    nameof(terminalApproachDistanceMeters),
                    "终点单向逼近范围必须大于终点位置容差。");
            }

            FinishDistanceMeters = finishDistanceMeters;
            FinishSpeedMetersPerSecond =
                finishSpeedMetersPerSecond;
            FinishHeadingToleranceRadians =
                finishHeadingToleranceRadians;
            MaximumDistanceToTrajectoryMeters =
                maximumDistanceToTrajectoryMeters;
            TerminalBrakingPreviewMeters =
                terminalBrakingPreviewMeters;
            TerminalApproachDistanceMeters =
                terminalApproachDistanceMeters;
            TerminalApproachGainPerSecond =
                terminalApproachGainPerSecond;
            MaximumTerminalApproachSpeedMetersPerSecond =
                maximumTerminalApproachSpeedMetersPerSecond;
            CurvaturePreviewSeconds = curvaturePreviewSeconds;
            MaximumCurvaturePreviewMeters =
                maximumCurvaturePreviewMeters;
            _motionDirectionInBodyRadians =
                AngleMath.NormalizeRadians(
                    motionDirectionInBodyRadians);
        }

        public double FinishDistanceMeters { get; }

        public double FinishSpeedMetersPerSecond { get; }

        public double FinishHeadingToleranceRadians { get; }

        public double MaximumDistanceToTrajectoryMeters { get; }

        public double TerminalBrakingPreviewMeters { get; }

        public double TerminalApproachDistanceMeters { get; }

        public double TerminalApproachGainPerSecond { get; }

        public double MaximumTerminalApproachSpeedMetersPerSecond { get; }

        public double CurvaturePreviewSeconds { get; }

        public double MaximumCurvaturePreviewMeters { get; }

        public bool IsActive { get; private set; }

        public bool IsCompleted { get; private set; }

        public string LastFailureReason { get; private set; } =
            string.Empty;

        public Exception LastException { get; private set; }

        public TrajectoryProjection? LastProjection { get; private set; }

        public GcpMotionCommand? LastRequestedCommand { get; private set; }

        public double? LastControlReferenceSpeedMetersPerSecond { get; private set; }

        public double? LastCurvaturePreviewDistanceMeters { get; private set; }

        public double? LastFeedforwardCurvaturePerMeter { get; private set; }

        // 重置跨周期状态，并从轨迹起点开始新的跟踪过程。
        public void Start(Trajectory2D trajectory)
        {
            if (trajectory == null)
            {
                throw new ArgumentNullException(
                    nameof(trajectory));
            }

            var terminalTravelDirection =
                ResolveTerminalTravelDirection(trajectory);

            ResetFeedbackControllers();
            _trajectory = trajectory;
            _terminalTravelDirection =
                terminalTravelDirection;
            IsActive = true;
            IsCompleted = false;
            ClearDiagnostics();
        }

        // 将受控刚体的位姿和速度转换为本周期GCP命令。
        public PathTrackingCycleOutput Compute(
            Pose2D poseInWorld,
            Twist2D actualTwistInBody,
            bool hasValidVelocityEstimate,
            double deltaTimeSeconds)
        {
            NumericGuard.EnsureFinite(
                poseInWorld,
                nameof(poseInWorld));
            NumericGuard.EnsureFinite(
                actualTwistInBody,
                nameof(actualTwistInBody));
            NumericGuard.EnsureFinitePositive(
                deltaTimeSeconds,
                nameof(deltaTimeSeconds));

            if (!IsActive || _trajectory == null)
            {
                return new PathTrackingCycleOutput(
                    PathTrackingCycleResult.Inactive,
                    null,
                    LastProjection,
                    0.0,
                    0.0);
            }

            var projectionStartTimestamp =
                Stopwatch.GetTimestamp();
            var projectionCompleted = false;
            var projectionMilliseconds = 0.0;
            var controllerComputeStartTimestamp = 0L;

            try
            {
                var projection = LastProjection.HasValue
                    ? TrajectoryProjector.Project(
                        _trajectory,
                        poseInWorld,
                        LastProjection.Value.ArcLengthMeters,
                        ProjectionBackwardSearchDistanceMeters,
                        ProjectionForwardSearchDistanceMeters)
                    : TrajectoryProjector.Project(
                        _trajectory,
                        poseInWorld);

                projectionMilliseconds =
                    GetElapsedMilliseconds(
                        projectionStartTimestamp);
                projectionCompleted = true;
                LastProjection = projection;
                controllerComputeStartTimestamp =
                    Stopwatch.GetTimestamp();

                if (projection.DistanceToTrajectoryMeters >
                    MaximumDistanceToTrajectoryMeters)
                {
                    Fail(
                        "受控刚体距离参考轨迹" +
                        $"{projection.DistanceToTrajectoryMeters:F3}m，" +
                        "超过允许值" +
                        $"{MaximumDistanceToTrajectoryMeters:F3}m。");
                    return CreateOutput(
                        PathTrackingCycleResult.Faulted,
                        null,
                        projection,
                        projectionMilliseconds,
                        controllerComputeStartTimestamp);
                }

                if (HasReachedEnd(
                        poseInWorld,
                        actualTwistInBody,
                        hasValidVelocityEstimate,
                        projection))
                {
                    CompleteTrajectory();
                    return CreateOutput(
                        PathTrackingCycleResult.Completed,
                        null,
                        projection,
                        projectionMilliseconds,
                        controllerComputeStartTimestamp);
                }

                if (HasStoppedAtUnsatisfiedTerminal(
                        poseInWorld,
                        actualTwistInBody,
                        hasValidVelocityEstimate,
                        projection,
                        out var terminalFailureReason))
                {
                    Fail(terminalFailureReason);
                    return CreateOutput(
                        PathTrackingCycleResult.Faulted,
                        null,
                        projection,
                        projectionMilliseconds,
                        controllerComputeStartTimestamp);
                }

                var controlReferenceSpeedMetersPerSecond =
                    ResolveControlReferenceSpeed(
                        poseInWorld,
                        projection);
                LastControlReferenceSpeedMetersPerSecond =
                    controlReferenceSpeedMetersPerSecond;
                var curvaturePreviewDistanceMeters =
                    ResolveCurvaturePreviewDistanceMeters(
                        actualTwistInBody,
                        hasValidVelocityEstimate,
                        controlReferenceSpeedMetersPerSecond);
                LastCurvaturePreviewDistanceMeters =
                    curvaturePreviewDistanceMeters;
                var feedforwardCurvaturePerMeter =
                    ResolveFeedforwardCurvaturePerMeter(
                        projection,
                        curvaturePreviewDistanceMeters);
                LastFeedforwardCurvaturePerMeter =
                    feedforwardCurvaturePerMeter;
                var context = new PathTrackingContext(
                    actualTwistInBody,
                    hasValidVelocityEstimate,
                    projection,
                    controlReferenceSpeedMetersPerSecond,
                    feedforwardCurvaturePerMeter,
                    deltaTimeSeconds,
                    _motionDirectionInBodyRadians);
                var lateralCommand =
                    _lateralController.Compute(context);
                var commandSpeedMetersPerSecond =
                    _longitudinalController
                        .ComputeSpeedMetersPerSecond(context);
                var command = _gcpAllocator.Allocate(
                    commandSpeedMetersPerSecond,
                    lateralCommand);

                LastRequestedCommand = command;
                LastFailureReason = string.Empty;
                LastException = null;

                return CreateOutput(
                    PathTrackingCycleResult.CommandGenerated,
                    command,
                    projection,
                    projectionMilliseconds,
                    controllerComputeStartTimestamp);
            }
            catch (Exception exception)
            {
                if (!projectionCompleted)
                {
                    projectionMilliseconds =
                        GetElapsedMilliseconds(
                            projectionStartTimestamp);
                }

                Fail(
                    "轨迹跟踪核心计算异常：" +
                    exception.Message,
                    exception);

                return CreateOutput(
                    PathTrackingCycleResult.Faulted,
                    null,
                    LastProjection,
                    projectionMilliseconds,
                    controllerComputeStartTimestamp);
            }
        }

        // 状态暂不可用时重置反馈历史，但保留当前轨迹和投影进度等待恢复。
        public void PauseForUnavailableState(string reason)
        {
            ResetFeedbackControllers();
            LastRequestedCommand = null;
            LastFailureReason = reason ?? string.Empty;
            LastException = null;
        }

        // 将外层执行故障同步到公共核心，并终止当前轨迹。
        public void Fail(
            string reason,
            Exception exception = null)
        {
            ResetFeedbackControllers();
            _trajectory = null;
            IsActive = false;
            IsCompleted = false;
            LastRequestedCommand = null;
            LastFailureReason = reason ?? string.Empty;
            LastException = exception;
        }

        // 取消当前轨迹并清除全部跟踪状态。
        public void Cancel()
        {
            ResetFeedbackControllers();
            _trajectory = null;
            _terminalTravelDirection = 1.0;
            IsActive = false;
            IsCompleted = false;
            ClearDiagnostics();
        }

        private double ResolveControlReferenceSpeed(
            Pose2D poseInWorld,
            TrajectoryProjection projection)
        {
            if (projection.RemainingDistanceMeters >
                TerminalApproachDistanceMeters)
            {
                return ResolveReferenceSpeedForControl(
                    projection);
            }

            return ResolveTerminalApproachSpeed(
                poseInWorld);
        }

        private double ResolveCurvaturePreviewDistanceMeters(
            Twist2D actualTwistInBody,
            bool hasValidVelocityEstimate,
            double controlReferenceSpeedMetersPerSecond)
        {
            if (CurvaturePreviewSeconds <= 0.0 ||
                MaximumCurvaturePreviewMeters <= 0.0)
            {
                return 0.0;
            }

            var previewSpeedMetersPerSecond =
                hasValidVelocityEstimate
                    ? CalculateActualLongitudinalSpeedMetersPerSecond(
                        actualTwistInBody)
                    : Math.Abs(
                        controlReferenceSpeedMetersPerSecond);

            return Math.Min(
                MaximumCurvaturePreviewMeters,
                previewSpeedMetersPerSecond *
                CurvaturePreviewSeconds);
        }

        private double ResolveFeedforwardCurvaturePerMeter(
            TrajectoryProjection projection,
            double previewDistanceMeters)
        {
            var previewArcLengthMeters = Math.Min(
                _trajectory.TotalLengthMeters,
                projection.ArcLengthMeters +
                previewDistanceMeters);

            return _trajectory
                .SampleAtArcLength(previewArcLengthMeters)
                .CurvaturePerMeter;
        }

        private double ResolveTerminalApproachSpeed(
            Pose2D poseInWorld)
        {
            var distanceToEndMeters =
                CalculateDistanceToEndMeters(
                    poseInWorld);
            var headingErrorToEndRadians =
                CalculateHeadingErrorToEndRadians(
                    poseInWorld);

            if (distanceToEndMeters <=
                    FinishDistanceMeters &&
                headingErrorToEndRadians <=
                    FinishHeadingToleranceRadians)
            {
                return 0.0;
            }

            var endPose = _trajectory.EndPoint.PoseInWorld;
            var deltaX = endPose.XMeters -
                         poseInWorld.XMeters;
            var deltaY = endPose.YMeters -
                         poseInWorld.YMeters;
            var longitudinalErrorMeters =
                deltaX * Math.Cos(endPose.YawRadians) +
                deltaY * Math.Sin(endPose.YawRadians);
            var remainingAlongTravelMeters =
                _terminalTravelDirection *
                longitudinalErrorMeters;

            // 越过终点后不生成与原轨迹方向相反的修正速度。
            if (remainingAlongTravelMeters <= 0.0)
            {
                return 0.0;
            }

            var speedMagnitudeMetersPerSecond =
                Math.Min(
                    MaximumTerminalApproachSpeedMetersPerSecond,
                    TerminalApproachGainPerSecond *
                    remainingAlongTravelMeters);

            return _terminalTravelDirection *
                   speedMagnitudeMetersPerSecond;
        }

        private double ResolveReferenceSpeedForControl(
            TrajectoryProjection projection)
        {
            var currentReferenceSpeed =
                ApplyTerminalBrakingPreview(
                    projection,
                    projection.ReferencePoint
                        .ReferenceSpeedMetersPerSecond);
            var isInStartupRegion =
                projection.ArcLengthMeters <=
                    StartupRegionMeters &&
                projection.RemainingDistanceMeters >
                    FinishDistanceMeters;

            if (!isInStartupRegion)
            {
                return currentReferenceSpeed;
            }

            var previewArcLengthMeters = Math.Min(
                _trajectory.TotalLengthMeters,
                projection.ArcLengthMeters +
                StartupPreviewDistanceMeters);
            var previewReferenceSpeed =
                _trajectory
                    .SampleAtArcLength(previewArcLengthMeters)
                    .ReferenceSpeedMetersPerSecond;

            if (Math.Abs(previewReferenceSpeed) <=
                ZeroReferenceSpeedToleranceMetersPerSecond)
            {
                return currentReferenceSpeed;
            }

            var startupReleaseSpeed =
                Math.Sign(previewReferenceSpeed) *
                Math.Min(
                    Math.Abs(previewReferenceSpeed),
                    MaximumStartupSpeedMetersPerSecond);

            if (Math.Sign(currentReferenceSpeed) ==
                    Math.Sign(startupReleaseSpeed) &&
                Math.Abs(currentReferenceSpeed) >=
                    Math.Abs(startupReleaseSpeed))
            {
                return currentReferenceSpeed;
            }

            return startupReleaseSpeed;
        }

        private double ApplyTerminalBrakingPreview(
            TrajectoryProjection projection,
            double currentReferenceSpeed)
        {
            if (TerminalBrakingPreviewMeters <= 0.0)
            {
                return currentReferenceSpeed;
            }

            var previewArcLengthMeters = Math.Min(
                _trajectory.TotalLengthMeters,
                projection.ArcLengthMeters +
                TerminalBrakingPreviewMeters);
            var previewReferenceSpeed =
                _trajectory
                    .SampleAtArcLength(previewArcLengthMeters)
                    .ReferenceSpeedMetersPerSecond;
            var previewIsStop =
                Math.Abs(previewReferenceSpeed) <=
                ZeroReferenceSpeedToleranceMetersPerSecond;
            var hasSameDirection =
                Math.Sign(previewReferenceSpeed) ==
                Math.Sign(currentReferenceSpeed);
            var previewIsSlower =
                Math.Abs(previewReferenceSpeed) <
                Math.Abs(currentReferenceSpeed);

            if (previewIsSlower &&
                (previewIsStop || hasSameDirection))
            {
                return previewReferenceSpeed;
            }

            return currentReferenceSpeed;
        }

        private static double ResolveTerminalTravelDirection(
            Trajectory2D trajectory)
        {
            for (var index = trajectory.Count - 1;
                 index >= 0;
                 index--)
            {
                var referenceSpeedMetersPerSecond =
                    trajectory[index]
                        .ReferenceSpeedMetersPerSecond;

                if (Math.Abs(referenceSpeedMetersPerSecond) >
                    ZeroReferenceSpeedToleranceMetersPerSecond)
                {
                    return Math.Sign(
                        referenceSpeedMetersPerSecond);
                }
            }

            throw new ArgumentException(
                "轨迹必须在终点前包含至少一个非零参考速度。",
                nameof(trajectory));
        }

        private bool HasReachedEnd(
            Pose2D poseInWorld,
            Twist2D actualTwistInBody,
            bool hasValidVelocityEstimate,
            TrajectoryProjection projection)
        {
            if (!hasValidVelocityEstimate)
            {
                return false;
            }

            return projection.RemainingDistanceMeters <=
                       FinishDistanceMeters &&
                   CalculateDistanceToEndMeters(poseInWorld) <=
                       FinishDistanceMeters &&
                   CalculateHeadingErrorToEndRadians(poseInWorld) <=
                       FinishHeadingToleranceRadians &&
                   CalculateActualLongitudinalSpeedMetersPerSecond(
                       actualTwistInBody) <=
                       FinishSpeedMetersPerSecond;
        }

        private bool HasStoppedAtUnsatisfiedTerminal(
            Pose2D poseInWorld,
            Twist2D actualTwistInBody,
            bool hasValidVelocityEstimate,
            TrajectoryProjection projection,
            out string failureReason)
        {
            failureReason = string.Empty;

            var isTerminalZeroSpeedReference =
                projection.RemainingDistanceMeters <=
                    FinishDistanceMeters &&
                Math.Abs(
                    projection.ReferencePoint
                        .ReferenceSpeedMetersPerSecond) <=
                    ZeroReferenceSpeedToleranceMetersPerSecond;

            if (!isTerminalZeroSpeedReference ||
                !hasValidVelocityEstimate ||
                CalculateActualLongitudinalSpeedMetersPerSecond(
                    actualTwistInBody) >
                FinishSpeedMetersPerSecond)
            {
                return false;
            }

            var positionErrorMeters =
                CalculateDistanceToEndMeters(
                    poseInWorld);
            var headingErrorRadians =
                CalculateHeadingErrorToEndRadians(
                    poseInWorld);

            failureReason =
                "受控刚体已在终点零速参考处停稳，但终点精度不满足要求：" +
                $"位置误差={positionErrorMeters:F3}m，" +
                "航向误差=" +
                $"{AngleMath.RadiansToDegrees(headingErrorRadians):F2}°。";
            return true;
        }

        private double CalculateDistanceToEndMeters(
            Pose2D poseInWorld)
        {
            var endPose = _trajectory.EndPoint.PoseInWorld;
            var deltaX = poseInWorld.XMeters -
                         endPose.XMeters;
            var deltaY = poseInWorld.YMeters -
                         endPose.YMeters;

            return Math.Sqrt(
                deltaX * deltaX +
                deltaY * deltaY);
        }

        private double CalculateHeadingErrorToEndRadians(
            Pose2D poseInWorld)
        {
            return Math.Abs(
                AngleMath.ShortestDifferenceRadians(
                    _trajectory.EndPoint
                        .PoseInWorld.YawRadians,
                    poseInWorld.YawRadians));
        }

        private double CalculateActualLongitudinalSpeedMetersPerSecond(
            Twist2D actualTwistInBody)
        {
            return Math.Abs(
                Math.Cos(_motionDirectionInBodyRadians) *
                    actualTwistInBody.VxMetersPerSecond +
                Math.Sin(_motionDirectionInBodyRadians) *
                    actualTwistInBody.VyMetersPerSecond);
        }

        private void CompleteTrajectory()
        {
            ResetFeedbackControllers();
            _trajectory = null;
            IsActive = false;
            IsCompleted = true;
            LastRequestedCommand = new GcpMotionCommand(
                0.0,
                0.0,
                0.0);
            LastFailureReason = string.Empty;
            LastException = null;
        }

        private void ResetFeedbackControllers()
        {
            _lateralController.Reset();
            _longitudinalController.Reset();
        }

        private void ClearDiagnostics()
        {
            LastProjection = null;
            LastRequestedCommand = null;
            LastControlReferenceSpeedMetersPerSecond = null;
            LastCurvaturePreviewDistanceMeters = null;
            LastFeedforwardCurvaturePerMeter = null;
            LastFailureReason = string.Empty;
            LastException = null;
        }

        private static PathTrackingCycleOutput CreateOutput(
            PathTrackingCycleResult result,
            GcpMotionCommand? command,
            TrajectoryProjection? projection,
            double projectionMilliseconds,
            long controllerComputeStartTimestamp)
        {
            var controllerComputeMilliseconds =
                controllerComputeStartTimestamp == 0L
                    ? 0.0
                    : GetElapsedMilliseconds(
                        controllerComputeStartTimestamp);

            return new PathTrackingCycleOutput(
                result,
                command,
                projection,
                projectionMilliseconds,
                controllerComputeMilliseconds);
        }

        private static double GetElapsedMilliseconds(
            long startTimestamp)
        {
            return (Stopwatch.GetTimestamp() - startTimestamp) *
                   1000.0 /
                   Stopwatch.Frequency;
        }
    }
}
