using System;
using System.Numerics;
using CommonUsage.Chassis;
using MultiWheelC.Control.Abstractions;
using MultiWheelC.Control.Allocation;
using MultiWheelC.Fleet;
using MultiWheelC.StateEstimation;
using MultiWheelC.Trajectory;
using MyParking.Shared;

namespace MultiWheelC.Tests
{
    internal static class FleetRuntimeTests
    {
        private const long PlanId = 21;

        public static void Run()
        {
            VerifyTwoVehiclePlanBecomesActive();
            VerifyStaleCommandIsIgnored();
            VerifyInvalidCommandLatchesMemberFault();
            VerifyMissingReportFaultsLeader();
            VerifyMemberWatchdogStopsLocally();
            VerifyUnavailableMemberStateFaultsFleet();

            Console.WriteLine(
                "FleetRuntime端到端测试通过，共6个场景。");
        }

        private static void VerifyTwoVehiclePlanBecomesActive()
        {
            var fleet = CreateFleet();
            StartAndActivate(fleet);

            AssertState(
                fleet.LeaderRuntime,
                FleetRuntimeState.Active,
                "主车正常激活");
            AssertState(
                fleet.MemberRuntime,
                FleetRuntimeState.Active,
                "从车正常激活");
            AssertTrue(
                fleet.LeaderRuntime.LastCoordinationOutput != null,
                "主车激活后应产生首周期协调输出");
            AssertTrue(
                fleet.MemberRuntime.LastAppliedCommandSequence > 0,
                "从车应确认已经执行主车命令");
        }

        private static void VerifyStaleCommandIsIgnored()
        {
            var fleet = CreateFleet();
            StartAndActivate(fleet);
            var appliedSequence =
                fleet.MemberRuntime.LastAppliedCommandSequence;

            fleet.LeaderTransport.SendCommand(
                new FleetCommand(
                    PlanId,
                    appliedSequence,
                    targetVehicleId: 2,
                    FleetCommandKind.Motion,
                    motionDirectionInBodyRadians: 0.0,
                    twistInVehicleBody:
                        new Twist2D(1.0, 0.0, 0.0),
                    validForSeconds: 0.2));
            fleet.MemberProvider.SetTimestamp(0.04);
            fleet.MemberRuntime.Update(0.04, 0.02);

            AssertState(
                fleet.MemberRuntime,
                FleetRuntimeState.Active,
                "忽略旧序列命令");
            AssertTrue(
                fleet.MemberRuntime.LastAppliedCommandSequence ==
                appliedSequence,
                "旧序列命令不应更新最近执行序号");
        }

        private static void VerifyMissingReportFaultsLeader()
        {
            var fleet = CreateFleet();
            AssertTrue(
                fleet.LeaderRuntime.StartRollingPlan(
                    PlanId,
                    fleet.Layout,
                    CreateTrajectory()),
                "主车应成功启动测试任务");

            fleet.LeaderRuntime.Update(0.0, 0.02);
            fleet.LeaderProvider.SetTimestamp(0.25);
            fleet.LeaderRuntime.Update(0.25, 0.02);

            AssertState(
                fleet.LeaderRuntime,
                FleetRuntimeState.Faulted,
                "成员报告超时");
            AssertTrue(
                fleet.LeaderRuntime.LastFailureReason.Contains(
                    "未收到成员车2"),
                "通信超时应指出缺失的成员车");
            AssertTrue(
                fleet.LeaderAgent.State ==
                FleetMemberAgentState.Idle,
                "主车故障后必须立即停止本车执行器");
        }

        private static void VerifyInvalidCommandLatchesMemberFault()
        {
            var fleet = CreateFleet();
            StartAndActivate(fleet);

            fleet.LeaderTransport.SendCommand(
                new FleetCommand(
                    PlanId,
                    fleet.MemberRuntime.LastAppliedCommandSequence + 1,
                    targetVehicleId: 2,
                    FleetCommandKind.Motion,
                    motionDirectionInBodyRadians: 0.0,
                    twistInVehicleBody: Twist2D.Zero,
                    validForSeconds: double.NaN));
            fleet.MemberRuntime.Update(0.04, 0.02);

            AssertState(
                fleet.MemberRuntime,
                FleetRuntimeState.Faulted,
                "非法命令字段触发并锁存本地故障");
        }

        private static void VerifyMemberWatchdogStopsLocally()
        {
            var fleet = CreateFleet();
            StartAndActivate(fleet);

            fleet.MemberProvider.SetTimestamp(0.25);
            fleet.MemberRuntime.Update(0.25, 0.02);

            AssertState(
                fleet.MemberRuntime,
                FleetRuntimeState.Faulted,
                "从车命令看门狗超时");
            AssertTrue(
                fleet.MemberRuntime.LastFailureReason.Contains(
                    "超时"),
                "从车看门狗应保留超时原因");
        }

        private static void VerifyUnavailableMemberStateFaultsFleet()
        {
            var fleet = CreateFleet();
            StartAndActivate(fleet);

            fleet.MemberProvider.IsAvailable = false;
            fleet.MemberRuntime.Update(0.04, 0.02);
            fleet.LeaderProvider.SetTimestamp(0.04);
            fleet.LeaderRuntime.Update(0.04, 0.02);

            AssertState(
                fleet.MemberRuntime,
                FleetRuntimeState.Faulted,
                "从车状态不可用时本地停车");
            AssertState(
                fleet.LeaderRuntime,
                FleetRuntimeState.Faulted,
                "从车状态不可用时整队停车");
        }

        private static void StartAndActivate(TestFleet fleet)
        {
            AssertTrue(
                fleet.LeaderRuntime.StartRollingPlan(
                    PlanId,
                    fleet.Layout,
                    CreateTrajectory()),
                "主车应成功启动测试任务");

            fleet.MemberRuntime.Update(0.0, 0.02);
            fleet.LeaderRuntime.Update(0.0, 0.02);
            fleet.MemberProvider.SetTimestamp(0.02);
            fleet.MemberRuntime.Update(0.02, 0.02);
        }

        private static TestFleet CreateFleet()
        {
            var layout = new FleetLayout(
                new[]
                {
                    new VehicleLayout(
                        1,
                        new Pose2D(-0.5, 0.0, 0.0)),
                    new VehicleLayout(
                        2,
                        new Pose2D(0.5, 0.0, 0.0))
                });
            var network = new InMemoryFleetTransportNetwork(
                new[] { 1, 2 },
                leaderVehicleId: 1);
            var leaderTransport = network.CreateEndpoint(1);
            var memberTransport = network.CreateEndpoint(2);
            var leaderProvider = new MutableStateProvider(
                new Pose2D(-0.5, 0.0, 0.0));
            var memberProvider = new MutableStateProvider(
                new Pose2D(0.5, 0.0, 0.0));
            var leaderAgent = CreateAgent(1);
            var memberAgent = CreateAgent(2);

            var leaderRuntime = new FleetRuntime(
                selfVehicleId: 1,
                leaderVehicleId: 1,
                leaderTransport,
                leaderAgent,
                leaderProvider,
                new FleetPreparationCoordinator(),
                CreateCoordinator(),
                new FleetSafetySupervisor(
                    communicationTimeoutSeconds: 0.2),
                commandValidForSeconds: 0.2,
                preparationTimeoutSeconds: 1.0);
            var memberRuntime = new FleetRuntime(
                selfVehicleId: 2,
                leaderVehicleId: 1,
                memberTransport,
                memberAgent,
                memberProvider,
                commandValidForSeconds: 0.2);

            return new TestFleet(
                layout,
                leaderTransport,
                leaderProvider,
                memberProvider,
                leaderAgent,
                leaderRuntime,
                memberRuntime);
        }

        private static FleetCoordinator CreateCoordinator()
        {
            return new FleetCoordinator(
                new FleetStateEstimator(
                    maximumMemberStateAgeSeconds: 0.5,
                    maximumPositionDisagreementMeters: 0.2,
                    maximumYawDisagreementRadians:
                        AngleMath.DegreesToRadians(5.0)),
                new FleetController(
                    new StraightLateralController(),
                    new ZeroLongitudinalController(),
                    new GcpCommandAllocator(Math.PI / 4.0),
                    virtualControlPointRadiusMeters: 0.5),
                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)),
                memberPositionErrorWarningMeters: 0.02,
                maximumMemberPositionErrorMeters: 0.05,
                memberYawErrorWarningRadians:
                    AngleMath.DegreesToRadians(1.0),
                maximumMemberYawErrorRadians:
                    AngleMath.DegreesToRadians(3.0));
        }

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

        private static FleetMemberAgent CreateAgent(int vehicleId)
        {
            var chassis = new MultiWheelChassis();
            chassis.AddWheel(CreateWheel(-500f, 300f));
            chassis.AddWheel(CreateWheel(-500f, -300f));
            chassis.AddWheel(CreateWheel(500f, 300f));
            chassis.AddWheel(CreateWheel(500f, -300f));
            chassis.Initialize();

            return new FleetMemberAgent(
                new MultiWheelChassisAdapter(chassis, vehicleId),
                alignmentToleranceRadians:
                    AngleMath.DegreesToRadians(1.0),
                alignmentStableSeconds: 0.0);
        }

        private static SteerWheel CreateWheel(float x, float y)
        {
            var speed = 0f;
            var angle = 0f;
            return new SteerWheel(
                new Vector2(x, y),
                angleLowerLimit: -120f,
                angleUpperLimit: 120f,
                speedWriter: value => speed = value,
                speedReader: () => speed,
                angleWriter: value => angle = value,
                angleReader: () => angle);
        }

        private static void AssertState(
            FleetRuntime runtime,
            FleetRuntimeState expected,
            string scenario)
        {
            AssertTrue(
                runtime.State == expected,
                $"{scenario}状态错误：" +
                $"actual={runtime.State}, expected={expected}，" +
                $"reason={runtime.LastFailureReason}");
        }

        private static void AssertTrue(bool condition, string message)
        {
            if (!condition)
            {
                throw new InvalidOperationException(message);
            }
        }

        private sealed class MutableStateProvider :
            IVehicleStateProvider
        {
            private readonly Pose2D _poseInWorld;
            private double _timestampSeconds;

            public MutableStateProvider(Pose2D poseInWorld)
            {
                _poseInWorld = poseInWorld;
                IsAvailable = true;
            }

            public bool IsAvailable { get; set; }

            public void SetTimestamp(double timestampSeconds)
            {
                _timestampSeconds = timestampSeconds;
            }

            public bool TryGetState(out VehicleState state)
            {
                state = new VehicleState(
                    _timestampSeconds,
                    _poseInWorld,
                    Twist2D.Zero,
                    hasValidVelocityEstimate: true);
                return IsAvailable;
            }
        }

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

            public void Reset()
            {
            }
        }

        private sealed class ZeroLongitudinalController :
            ILongitudinalController
        {
            public double ComputeSpeedMetersPerSecond(
                PathTrackingContext context)
            {
                return 0.0;
            }

            public void Reset()
            {
            }
        }

        private sealed class TestFleet
        {
            public TestFleet(
                FleetLayout layout,
                InMemoryFleetTransport leaderTransport,
                MutableStateProvider leaderProvider,
                MutableStateProvider memberProvider,
                FleetMemberAgent leaderAgent,
                FleetRuntime leaderRuntime,
                FleetRuntime memberRuntime)
            {
                Layout = layout;
                LeaderTransport = leaderTransport;
                LeaderProvider = leaderProvider;
                MemberProvider = memberProvider;
                LeaderAgent = leaderAgent;
                LeaderRuntime = leaderRuntime;
                MemberRuntime = memberRuntime;
            }

            public FleetLayout Layout { get; }

            public InMemoryFleetTransport LeaderTransport { get; }

            public MutableStateProvider LeaderProvider { get; }

            public MutableStateProvider MemberProvider { get; }

            public FleetMemberAgent LeaderAgent { get; }

            public FleetRuntime LeaderRuntime { get; }

            public FleetRuntime MemberRuntime { get; }
        }
    }
}
