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

namespace MultiWheelC.Tests
{
    internal static class FleetControllerTests
    {
        private const double Tolerance = 1e-9;

        public static void Run()
        {
            VerifyInactiveControllerStops();
            VerifyStraightCommand();
            VerifyFortyFiveDegreeMotionDirection();
            VerifyNinetyDegreeMotionDirection();
            VerifyInvalidVelocityUsesReferenceSpeed();
            VerifyCompletionStops();
            VerifyExcessiveTrackingErrorFaults();
            VerifyCancelStops();

            Console.WriteLine(
                "FleetController车队中心控制测试通过。共8个场景。");
        }

        private static void VerifyInactiveControllerStops()
        {
            var controller = CreateController();
            var result = controller.ComputeCommand(
                CreateState(0.5, 0.0, 0.0, true),
                0.02,
                out var command);

            AssertResult(
                result,
                FleetControlCycleResult.Inactive,
                "未启动控制器");
            AssertStop(command, "未启动控制器");
        }

        private static void VerifyStraightCommand()
        {
            var controller = CreateController();
            controller.Start(CreateStraightTrajectory());

            var result = controller.ComputeCommand(
                CreateState(0.5, 0.0, 0.4, true),
                0.02,
                out var command);

            AssertResult(
                result,
                FleetControlCycleResult.CommandGenerated,
                "直线控制");
            AssertNear(
                command.ReferencePointInFleet.XMeters,
                0.0,
                "直线控制参考点X");
            AssertNear(
                command.ReferencePointInFleet.YMeters,
                0.0,
                "直线控制参考点Y");
            AssertTwist(
                command.TwistAtReferencePoint,
                0.4,
                0.0,
                0.0,
                "直线控制");
        }

        private static void VerifyInvalidVelocityUsesReferenceSpeed()
        {
            var controller = CreateController();
            controller.Start(CreateStraightTrajectory());

            var result = controller.ComputeCommand(
                CreateState(0.5, 0.0, 0.0, false),
                0.02,
                out var command);

            AssertResult(
                result,
                FleetControlCycleResult.CommandGenerated,
                "速度尚未初始化");
            AssertTwist(
                command.TwistAtReferencePoint,
                0.4,
                0.0,
                0.0,
                "速度尚未初始化");
        }

        private static void VerifyFortyFiveDegreeMotionDirection()
        {
            VerifyMotionDirection(
                Math.PI / 4.0,
                "45度运动方向");
        }

        private static void VerifyNinetyDegreeMotionDirection()
        {
            VerifyMotionDirection(
                Math.PI / 2.0,
                "90度运动方向");
        }

        private static void VerifyMotionDirection(
            double motionDirectionInFleetRadians,
            string scenario)
        {
            var controller = CreateController(
                motionDirectionInFleetRadians);
            controller.Start(
                CreateStraightTrajectory(
                    motionDirectionInFleetRadians));

            var directionX =
                Math.Cos(motionDirectionInFleetRadians);
            var directionY =
                Math.Sin(motionDirectionInFleetRadians);
            var result = controller.ComputeCommand(
                CreateState(
                    0.5 * directionX,
                    0.5 * directionY,
                    0.4 * directionX,
                    0.4 * directionY,
                    true),
                0.02,
                out var command);

            AssertResult(
                result,
                FleetControlCycleResult.CommandGenerated,
                scenario);
            AssertTwist(
                command.TwistAtReferencePoint,
                0.4 * directionX,
                0.4 * directionY,
                0.0,
                scenario);
        }

        private static void VerifyCompletionStops()
        {
            var controller = CreateController();
            controller.Start(CreateStraightTrajectory());

            var result = controller.ComputeCommand(
                CreateState(1.0, 0.0, 0.0, true),
                0.02,
                out var command);

            AssertResult(
                result,
                FleetControlCycleResult.Completed,
                "终点完成");
            AssertStop(command, "终点完成");

            if (controller.IsActive || !controller.IsCompleted)
            {
                throw new InvalidOperationException(
                    "终点完成后控制器状态错误。");
            }
        }

        private static void VerifyExcessiveTrackingErrorFaults()
        {
            var controller = CreateController();
            controller.Start(CreateStraightTrajectory());

            var result = controller.ComputeCommand(
                CreateState(0.5, 0.5, 0.0, true),
                0.02,
                out var command);

            AssertResult(
                result,
                FleetControlCycleResult.Faulted,
                "轨迹偏离保护");
            AssertStop(command, "轨迹偏离保护");

            if (string.IsNullOrWhiteSpace(
                    controller.LastFailureReason))
            {
                throw new InvalidOperationException(
                    "轨迹偏离故障没有保存原因。");
            }
        }

        private static void VerifyCancelStops()
        {
            var controller = CreateController();
            controller.Start(CreateStraightTrajectory());
            controller.Cancel();

            var result = controller.ComputeCommand(
                CreateState(0.5, 0.0, 0.0, true),
                0.02,
                out var command);

            AssertResult(
                result,
                FleetControlCycleResult.Inactive,
                "取消控制");
            AssertStop(command, "取消控制");
        }

        private static FleetController CreateController(
            double motionDirectionInFleetRadians = 0.0)
        {
            return new FleetController(
                new StraightLateralController(),
                new ReferenceLongitudinalController(),
                new GcpCommandAllocator(
                    Math.PI / 4.0),
                virtualControlPointRadiusMeters: 0.5,
                motionDirectionInFleetRadians:
                    motionDirectionInFleetRadians);
        }

        private static Trajectory2D CreateStraightTrajectory(
            double motionDirectionInFleetRadians = 0.0)
        {
            var directionX =
                Math.Cos(motionDirectionInFleetRadians);
            var directionY =
                Math.Sin(motionDirectionInFleetRadians);

            return new Trajectory2D(
                new[]
                {
                    new TrajectoryPoint(
                        0.0,
                        Pose2D.Identity,
                        0.0,
                        0.4),
                    new TrajectoryPoint(
                        1.0,
                        new Pose2D(
                            directionX,
                            directionY,
                            0.0),
                        0.0,
                        0.4)
                });
        }

        private static FleetState CreateState(
            double xMeters,
            double yMeters,
            double vxMetersPerSecond,
            bool hasValidVelocityEstimate)
        {
            return CreateState(
                xMeters,
                yMeters,
                vxMetersPerSecond,
                0.0,
                hasValidVelocityEstimate);
        }

        private static FleetState CreateState(
            double xMeters,
            double yMeters,
            double vxMetersPerSecond,
            double vyMetersPerSecond,
            bool hasValidVelocityEstimate)
        {
            return new FleetState(
                sampleTimestampSeconds: 1.0,
                fleetPoseInWorld: new Pose2D(
                    xMeters,
                    yMeters,
                    0.0),
                twistAtFleetOriginInWorld: new Twist2D(
                    vxMetersPerSecond,
                    vyMetersPerSecond,
                    0.0),
                hasValidVelocityEstimate:
                    hasValidVelocityEstimate);
        }

        private static void AssertResult(
            FleetControlCycleResult actual,
            FleetControlCycleResult expected,
            string scenario)
        {
            if (actual != expected)
            {
                throw new InvalidOperationException(
                    $"{scenario}结果错误：" +
                    $"actual={actual}, expected={expected}。");
            }
        }

        private static void AssertStop(
            FleetMotionCommand command,
            string scenario)
        {
            AssertTwist(
                command.TwistAtReferencePoint,
                0.0,
                0.0,
                0.0,
                scenario);
        }

        private static void AssertTwist(
            Twist2D actual,
            double expectedVx,
            double expectedVy,
            double expectedOmega,
            string scenario)
        {
            AssertNear(
                actual.VxMetersPerSecond,
                expectedVx,
                $"{scenario} Vx");
            AssertNear(
                actual.VyMetersPerSecond,
                expectedVy,
                $"{scenario} Vy");
            AssertNear(
                actual.OmegaRadiansPerSecond,
                expectedOmega,
                $"{scenario} Omega");
        }

        private static void AssertNear(
            double actual,
            double expected,
            string valueName)
        {
            if (Math.Abs(actual - expected) > Tolerance)
            {
                throw new InvalidOperationException(
                    $"{valueName}错误：" +
                    $"actual={actual:F9}, expected={expected:F9}。");
            }
        }

        private sealed class StraightLateralController :
            ILateralController
        {
            public LateralControlCommand Compute(
                PathTrackingContext context)
            {
                return LateralControlCommand.Straight;
            }

            public void Reset()
            {
            }
        }

        private sealed class ReferenceLongitudinalController :
            ILongitudinalController
        {
            public double ComputeSpeedMetersPerSecond(
                PathTrackingContext context)
            {
                return context
                    .ControlReferenceSpeedMetersPerSecond;
            }

            public void Reset()
            {
            }
        }
    }
}
