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

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

        public static void Run()
        {
            VerifyCommandCycle();
            VerifySmallLayoutErrorIsCorrected();
            VerifyWarningRangeScalesAllCommands();
            VerifyUnavailableStateStopsAndRecovers();
            VerifyUnsafeLayoutFaultsAndLatches();
            VerifyCompletionStops();
            VerifyTrackingFaultStops();
            VerifyCancelReturnsInactive();

            Console.WriteLine(
                "FleetCoordinator车队协调测试通过。共8个场景。");
        }

        private static void VerifyCommandCycle()
        {
            var layout = CreateLayout();
            var coordinator = CreateCoordinator();
            coordinator.Start(layout, CreateTrajectory());

            var result = coordinator.ExecuteCycle(
                CreateRigidMemberStates(
                    layout,
                    new Pose2D(0.5, 0.0, 0.0),
                    new Twist2D(0.4, 0.0, 0.0),
                    1.0),
                targetTimestampSeconds: 1.0,
                deltaTimeSeconds: 0.02,
                out var output);

            AssertResult(
                result,
                FleetCoordinationCycleResult.CommandGenerated,
                "正常协调周期");
            if (!output.State.HasValue)
            {
                throw new InvalidOperationException(
                    "正常协调周期没有返回车队状态。");
            }

            AssertNear(
                output.State.Value.FleetPoseInWorld.XMeters,
                0.5,
                "正常协调周期中心X");
            AssertNear(
                output.SpeedScale,
                1.0,
                "正常协调周期速度比例");
            AssertTwist(
                output.FleetCommand.TwistAtReferencePoint,
                0.4,
                0.0,
                0.0,
                "正常协调周期车队命令");
            AssertTwist(
                FindCommand(output.MemberCommands, 1)
                    .TwistInVehicleBody,
                0.4,
                0.0,
                0.0,
                "正常协调周期车辆1");
            AssertTwist(
                FindCommand(output.MemberCommands, 2)
                    .TwistInVehicleBody,
                -0.4,
                0.0,
                0.0,
                "正常协调周期车辆2");
        }

        private static void VerifySmallLayoutErrorIsCorrected()
        {
            var layout = CreateLayout();
            var coordinator = CreateCoordinator();
            coordinator.Start(layout, CreateTrajectory());
            var result = coordinator.ExecuteCycle(
                new[]
                {
                    CreateMemberState(
                        1,
                        new Pose2D(-0.99, 0.0, 0.0),
                        1.0),
                    CreateMemberState(
                        2,
                        new Pose2D(0.99, 0.0, Math.PI),
                        1.0)
                },
                targetTimestampSeconds: 1.0,
                deltaTimeSeconds: 0.02,
                out var output);

            AssertResult(
                result,
                FleetCoordinationCycleResult.CommandGenerated,
                "小范围布局误差纠偏");
            AssertNear(
                output.SpeedScale,
                1.0,
                "小范围布局误差速度比例");
            AssertTwist(
                FindCommand(output.BaseMemberCommands, 1)
                    .TwistInVehicleBody,
                0.4,
                0.0,
                0.0,
                "车辆1基础命令");
            AssertTwist(
                FindCommand(output.BaseMemberCommands, 2)
                    .TwistInVehicleBody,
                -0.4,
                0.0,
                0.0,
                "车辆2基础命令");
            AssertTwist(
                FindCommand(output.MemberCommands, 1)
                    .TwistInVehicleBody,
                0.395,
                0.0,
                0.0,
                "车辆1纠偏后命令");
            AssertTwist(
                FindCommand(output.MemberCommands, 2)
                    .TwistInVehicleBody,
                -0.405,
                0.0,
                0.0,
                "车辆2纠偏后命令");
        }

        private static void VerifyWarningRangeScalesAllCommands()
        {
            var layout = CreateLayout();
            var coordinator = CreateCoordinator();
            coordinator.Start(layout, CreateTrajectory());
            var result = coordinator.ExecuteCycle(
                new[]
                {
                    CreateMemberState(
                        1,
                        new Pose2D(-0.965, 0.0, 0.0),
                        1.0),
                    CreateMemberState(
                        2,
                        new Pose2D(0.965, 0.0, Math.PI),
                        1.0)
                },
                targetTimestampSeconds: 1.0,
                deltaTimeSeconds: 0.02,
                out var output);

            AssertResult(
                result,
                FleetCoordinationCycleResult.CommandGenerated,
                "警告区间统一缩放");
            AssertNear(
                output.SpeedScale,
                0.5,
                "警告区间速度比例");
            AssertTwist(
                output.FleetCommand.TwistAtReferencePoint,
                0.2,
                0.0,
                0.0,
                "警告区间车队命令");
            AssertTwist(
                FindCommand(output.MemberCommands, 1)
                    .TwistInVehicleBody,
                0.2,
                0.0,
                0.0,
                "警告区间车辆1");
            AssertTwist(
                FindCommand(output.MemberCommands, 2)
                    .TwistInVehicleBody,
                -0.2,
                0.0,
                0.0,
                "警告区间车辆2");

            if (string.IsNullOrWhiteSpace(output.Reason))
            {
                throw new InvalidOperationException(
                    "警告区间缩放没有返回限制原因。");
            }
        }

        private static void VerifyUnavailableStateStopsAndRecovers()
        {
            var layout = CreateLayout();
            var coordinator = CreateCoordinator();
            coordinator.Start(layout, CreateTrajectory());
            var unavailableStates = CreateRigidMemberStates(
                layout,
                new Pose2D(0.5, 0.0, 0.0),
                new Twist2D(0.4, 0.0, 0.0),
                1.0);
            unavailableStates[1] = new FleetMemberStateSample(
                unavailableStates[1].VehicleId,
                unavailableStates[1].SampleTimestampSeconds,
                unavailableStates[1].PoseInWorld,
                unavailableStates[1].TwistAtVehicleOriginInWorld,
                isStateAvailable: false,
                hasValidVelocityEstimate: true);

            var waitingResult = coordinator.ExecuteCycle(
                unavailableStates,
                targetTimestampSeconds: 1.0,
                deltaTimeSeconds: 0.02,
                out var waitingOutput);

            AssertResult(
                waitingResult,
                FleetCoordinationCycleResult.WaitingForState,
                "状态暂时不可用");
            AssertStop(waitingOutput, layout.VehicleCount);

            if (!coordinator.IsActive)
            {
                throw new InvalidOperationException(
                    "状态暂时不可用不应取消车队控制器。");
            }

            var recoveredResult = coordinator.ExecuteCycle(
                CreateRigidMemberStates(
                    layout,
                    new Pose2D(0.5, 0.0, 0.0),
                    new Twist2D(0.4, 0.0, 0.0),
                    1.02),
                targetTimestampSeconds: 1.02,
                deltaTimeSeconds: 0.02,
                out _);

            AssertResult(
                recoveredResult,
                FleetCoordinationCycleResult.CommandGenerated,
                "状态恢复");
        }

        private static void VerifyUnsafeLayoutFaultsAndLatches()
        {
            var layout = CreateLayout();
            var coordinator = CreateCoordinator();
            coordinator.Start(layout, CreateTrajectory());
            var deformedStates = new[]
            {
                CreateMemberState(
                    1,
                    new Pose2D(-0.9, 0.0, 0.0),
                    1.0),
                CreateMemberState(
                    2,
                    new Pose2D(0.9, 0.0, Math.PI),
                    1.0)
            };

            var result = coordinator.ExecuteCycle(
                deformedStates,
                targetTimestampSeconds: 1.0,
                deltaTimeSeconds: 0.02,
                out var output);

            AssertResult(
                result,
                FleetCoordinationCycleResult.Faulted,
                "布局误差超限");
            AssertStop(output, layout.VehicleCount);

            if (!coordinator.IsFaulted ||
                string.IsNullOrWhiteSpace(
                    coordinator.LastFailureReason))
            {
                throw new InvalidOperationException(
                    "布局误差故障没有被锁存。");
            }

            var latchedResult = coordinator.ExecuteCycle(
                CreateRigidMemberStates(
                    layout,
                    new Pose2D(0.5, 0.0, 0.0),
                    new Twist2D(0.4, 0.0, 0.0),
                    1.02),
                targetTimestampSeconds: 1.02,
                deltaTimeSeconds: 0.02,
                out var latchedOutput);

            AssertResult(
                latchedResult,
                FleetCoordinationCycleResult.Faulted,
                "布局误差故障锁存");
            AssertStop(latchedOutput, layout.VehicleCount);
        }

        private static void VerifyCompletionStops()
        {
            var layout = CreateLayout();
            var coordinator = CreateCoordinator();
            coordinator.Start(layout, CreateTrajectory());

            var result = coordinator.ExecuteCycle(
                CreateRigidMemberStates(
                    layout,
                    new Pose2D(1.0, 0.0, 0.0),
                    Twist2D.Zero,
                    1.0),
                targetTimestampSeconds: 1.0,
                deltaTimeSeconds: 0.02,
                out var output);

            AssertResult(
                result,
                FleetCoordinationCycleResult.Completed,
                "车队轨迹完成");
            AssertStop(output, layout.VehicleCount);

            if (!coordinator.IsCompleted)
            {
                throw new InvalidOperationException(
                    "车队轨迹完成状态没有被保存。");
            }
        }

        private static void VerifyTrackingFaultStops()
        {
            var layout = CreateLayout();
            var coordinator = CreateCoordinator();
            coordinator.Start(layout, CreateTrajectory());

            var result = coordinator.ExecuteCycle(
                CreateRigidMemberStates(
                    layout,
                    new Pose2D(0.5, 0.5, 0.0),
                    Twist2D.Zero,
                    1.0),
                targetTimestampSeconds: 1.0,
                deltaTimeSeconds: 0.02,
                out var output);

            AssertResult(
                result,
                FleetCoordinationCycleResult.Faulted,
                "车队中心跟踪故障");
            AssertStop(output, layout.VehicleCount);

            if (!coordinator.IsFaulted)
            {
                throw new InvalidOperationException(
                    "车队中心跟踪故障没有传递到协调器。");
            }
        }

        private static void VerifyCancelReturnsInactive()
        {
            var layout = CreateLayout();
            var coordinator = CreateCoordinator();
            coordinator.Start(layout, CreateTrajectory());
            coordinator.Cancel();

            var result = coordinator.ExecuteCycle(
                CreateRigidMemberStates(
                    layout,
                    new Pose2D(0.5, 0.0, 0.0),
                    Twist2D.Zero,
                    1.0),
                targetTimestampSeconds: 1.0,
                deltaTimeSeconds: 0.02,
                out var output);

            AssertResult(
                result,
                FleetCoordinationCycleResult.Inactive,
                "取消车队协调");
            AssertStop(output, layout.VehicleCount);
        }

        private static FleetCoordinator CreateCoordinator()
        {
            var estimator = new FleetStateEstimator(
                maximumMemberStateAgeSeconds: 0.25,
                maximumPositionDisagreementMeters: 0.5,
                maximumYawDisagreementRadians:
                    AngleMath.DegreesToRadians(10.0));
            var controller = new FleetController(
                new StraightLateralController(),
                new ReferenceLongitudinalController(),
                new GcpCommandAllocator(Math.PI / 4.0),
                virtualControlPointRadiusMeters: 0.5);
            var commandCorrector =
                new FleetMemberCommandCorrector(
                    longitudinalPositionGainPerSecond: 1.0,
                    lateralPositionGainPerSecond: 1.0,
                    yawGainPerSecond: 1.0,
                    positionErrorDeadbandMeters: 0.005,
                    yawErrorDeadbandRadians:
                        AngleMath.DegreesToRadians(0.5),
                    maximumLinearCorrectionMetersPerSecond:
                        0.03,
                    maximumAngularCorrectionRadiansPerSecond:
                        AngleMath.DegreesToRadians(2.0));

            return new FleetCoordinator(
                estimator,
                controller,
                commandCorrector,
                memberPositionErrorWarningMeters: 0.02,
                maximumMemberPositionErrorMeters: 0.05,
                memberYawErrorWarningRadians:
                    AngleMath.DegreesToRadians(1.0),
                maximumMemberYawErrorRadians:
                    AngleMath.DegreesToRadians(3.0));
        }

        private static FleetLayout CreateLayout()
        {
            return new FleetLayout(
                new[]
                {
                    new VehicleLayout(
                        1,
                        new Pose2D(-1.0, 0.0, 0.0)),
                    new VehicleLayout(
                        2,
                        new Pose2D(1.0, 0.0, Math.PI))
                });
        }

        private static Trajectory2D CreateTrajectory()
        {
            return new Trajectory2D(
                new[]
                {
                    new TrajectoryPoint(
                        0.0,
                        Pose2D.Identity,
                        0.0,
                        0.4),
                    new TrajectoryPoint(
                        1.0,
                        new Pose2D(1.0, 0.0, 0.0),
                        0.0,
                        0.4)
                });
        }

        private static FleetMemberStateSample[]
            CreateRigidMemberStates(
                FleetLayout layout,
                Pose2D fleetPoseInWorld,
                Twist2D twistAtFleetOriginInWorld,
                double timestampSeconds)
        {
            var states =
                new FleetMemberStateSample[layout.VehicleCount];

            for (var index = 0;
                 index < layout.Vehicles.Count;
                 index++)
            {
                var vehicle = layout.Vehicles[index];
                var poseInWorld = FrameTransform2D.Compose(
                    fleetPoseInWorld,
                    vehicle.PoseInFleet);
                var offsetX =
                    poseInWorld.XMeters -
                    fleetPoseInWorld.XMeters;
                var offsetY =
                    poseInWorld.YMeters -
                    fleetPoseInWorld.YMeters;
                var twistInWorld = new Twist2D(
                    twistAtFleetOriginInWorld.VxMetersPerSecond -
                    twistAtFleetOriginInWorld.OmegaRadiansPerSecond *
                    offsetY,
                    twistAtFleetOriginInWorld.VyMetersPerSecond +
                    twistAtFleetOriginInWorld.OmegaRadiansPerSecond *
                    offsetX,
                    twistAtFleetOriginInWorld.OmegaRadiansPerSecond);

                states[index] = new FleetMemberStateSample(
                    vehicle.VehicleId,
                    timestampSeconds,
                    poseInWorld,
                    twistInWorld,
                    isStateAvailable: true,
                    hasValidVelocityEstimate: true);
            }

            return states;
        }

        private static FleetMemberStateSample CreateMemberState(
            int vehicleId,
            Pose2D poseInWorld,
            double timestampSeconds)
        {
            return new FleetMemberStateSample(
                vehicleId,
                timestampSeconds,
                poseInWorld,
                Twist2D.Zero,
                isStateAvailable: true,
                hasValidVelocityEstimate: true);
        }

        private static FleetMemberCommand FindCommand(
            IReadOnlyList<FleetMemberCommand> commands,
            int vehicleId)
        {
            for (var index = 0;
                 index < commands.Count;
                 index++)
            {
                if (commands[index].VehicleId == vehicleId)
                {
                    return commands[index];
                }
            }

            throw new InvalidOperationException(
                $"没有找到车辆{vehicleId}的成员命令。");
        }

        private static void AssertStop(
            FleetCoordinationCycleOutput output,
            int expectedMemberCount)
        {
            AssertTwist(
                output.FleetCommand.TwistAtReferencePoint,
                0.0,
                0.0,
                0.0,
                "车队停止命令");

            if (output.MemberCommands.Count != expectedMemberCount)
            {
                throw new InvalidOperationException(
                    "停止输出的成员命令数量错误。");
            }

            if (output.BaseMemberCommands.Count != expectedMemberCount)
            {
                throw new InvalidOperationException(
                    "停止输出的成员基础命令数量错误。");
            }

            for (var index = 0;
                 index < output.MemberCommands.Count;
                 index++)
            {
                AssertTwist(
                    output.BaseMemberCommands[index]
                        .TwistInVehicleBody,
                    0.0,
                    0.0,
                    0.0,
                    "成员基础停止命令");
                AssertTwist(
                    output.MemberCommands[index].TwistInVehicleBody,
                    0.0,
                    0.0,
                    0.0,
                    "成员停止命令");
            }
        }

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

        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()
            {
            }
        }
    }
}
