using System;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Newtonsoft.Json.Linq;

namespace NavisworksTransport.UnitTests.Integration
{
    /// <summary>
    /// 动画逐帧状态验证集成测试（覆盖计划 T04）。
    ///
    /// 通过 probe-animation-frames 端点生成动画后 seek 到多个进度点，
    /// 断言物体跟踪位置（业务跟踪点 = 原始包围盒中心）沿路径移动：
    /// - Ground：起点/终点 XZ 匹配路径点，高度恒定（平面链不改变高度），中间帧在起终点之间
    /// - Hoisting：高度剖面（起吊地面 → 悬挂升高 → 下降地面）+ 水平平移
    ///
    /// 注：比较使用模型单位（Floor2 为英尺）；物体跟踪点 XZ 与路径点一致，Y 为中心高度（非路径点高度）。
    /// </summary>
    [TestClass]
    [TestCategory("NavisworksIntegration")]
    public class AnimationFrameProbeAutomationTests
    {
        private const double PositionTolerance = 1e-2;
        private const int TestFrameRate = 15;
        private const double TestDurationSeconds = 5.0;

        [TestMethod]
        [Timeout(240000)]
        public async Task GroundVirtualObject_FramesFollowPathStartToEnd()
        {
            using (var client = new NavisworksTestAutomationClient())
            {
                await client.EnsureServiceReadyAsync(TimeSpan.FromSeconds(90)).ConfigureAwait(false);

                JObject response = await client.ProbeAnimationFramesAsync(
                    "Ground",
                    120,
                    frameRate: TestFrameRate,
                    durationSeconds: TestDurationSeconds).ConfigureAwait(false);
                Assert.IsTrue(response.Value<bool>("ok"), "帧探针失败: " + (string)response["error"]);

                JObject data = (JObject)response["data"];
                Assert.AreEqual("自动测试_Ground", (string)data["route"]["name"], "探针路径不匹配");
                Assert.IsTrue((int)data["totalFrames"] > 0, "总帧数应大于 0");

                JObject pathStart = (JObject)data["pathStart"];
                JObject pathEnd = (JObject)data["pathEnd"];
                JArray probes = (JArray)data["probes"];

                AssertProbeCount(probes, 5);

                // probe 0：物体起点 = 路径起点（XZ 匹配，Y 为中心高度）
                var p0 = (JObject)probes[0]["position"];
                AssertNear((double)pathStart["x"], (double)p0["x"], PositionTolerance, "起点 X 不匹配路径起点");
                AssertNear((double)pathStart["z"], (double)p0["z"], PositionTolerance, "起点 Z 不匹配路径起点");

                // probe 1.0：物体终点 = 路径终点
                var p1 = (JObject)probes[probes.Count - 1]["position"];
                AssertNear((double)pathEnd["x"], (double)p1["x"], PositionTolerance, "终点 X 不匹配路径终点");
                AssertNear((double)pathEnd["z"], (double)p1["z"], PositionTolerance, "终点 Z 不匹配路径终点");

                // Ground 平面链：高度恒定（物体中心高度不变）
                AssertNear((double)p0["y"], (double)p1["y"], PositionTolerance, "Ground 路径高度应恒定");

                // 中间帧在起终点之间（水平距离大于 0 且小于全程）
                var pm = (JObject)probes[probes.Count / 2]["position"];
                double startToMid = HorizontalDistance(p0, pm);
                double startToEnd = HorizontalDistance(p0, p1);
                Assert.IsTrue(startToMid > PositionTolerance, "中间帧不应等于起点");
                Assert.IsTrue(startToMid < startToEnd - PositionTolerance, "中间帧应在起终点之间");

                // yaw 应为有限值
                foreach (var probe in probes)
                {
                    Assert.IsFalse(double.IsNaN((double)probe["yawDegrees"]), "yaw 不应为 NaN");
                }
            }
        }

        [TestMethod]
        [Timeout(240000)]
        public async Task HoistingVirtualObject_FramesFollowLiftProfile()
        {
            using (var client = new NavisworksTestAutomationClient())
            {
                await client.EnsureServiceReadyAsync(TimeSpan.FromSeconds(90)).ConfigureAwait(false);

                JObject response = await client.ProbeAnimationFramesAsync(
                    "Hoisting",
                    120,
                    frameRate: TestFrameRate,
                    durationSeconds: TestDurationSeconds).ConfigureAwait(false);
                Assert.IsTrue(response.Value<bool>("ok"), "帧探针失败: " + (string)response["error"]);

                JObject data = (JObject)response["data"];
                Assert.AreEqual("自动测试_Hoisting", (string)data["route"]["name"], "探针路径不匹配");
                Assert.IsTrue((int)data["totalFrames"] > 0, "总帧数应大于 0");

                JObject pathStart = (JObject)data["pathStart"];
                JObject pathEnd = (JObject)data["pathEnd"];
                JArray probes = (JArray)data["probes"];

                AssertProbeCount(probes, 5);

                var p0 = (JObject)probes[0]["position"];
                var pm = (JObject)probes[probes.Count / 2]["position"];
                var p1 = (JObject)probes[probes.Count - 1]["position"];

                // 起吊点/落地点：XZ 匹配路径起点/终点（地面）
                AssertNear((double)pathStart["x"], (double)p0["x"], PositionTolerance, "起吊点 X 不匹配");
                AssertNear((double)pathStart["z"], (double)p0["z"], PositionTolerance, "起吊点 Z 不匹配");
                AssertNear((double)pathEnd["x"], (double)p1["x"], PositionTolerance, "落地点 X 不匹配");
                AssertNear((double)pathEnd["z"], (double)p1["z"], PositionTolerance, "落地点 Z 不匹配");

                // 吊装剖面：中间帧高度高于起吊/落地（悬挂段），结束后回落
                Assert.IsTrue((double)pm["y"] > (double)p0["y"] + PositionTolerance, "中间帧应高于起吊点（悬挂升高）");
                AssertNear((double)p0["y"], (double)p1["y"], PositionTolerance, "落地后高度应回落到起吊高度");

                // 水平平移：中间帧 X 在起点与终点之间
                double startX = (double)p0["x"];
                double endX = (double)p1["x"];
                double midX = (double)pm["x"];
                Assert.IsTrue(
                    midX > Math.Min(startX, endX) - PositionTolerance &&
                    midX < Math.Max(startX, endX) + PositionTolerance,
                    "中间帧 X 应在起终点之间（水平平移）");
            }
        }

        private static void AssertProbeCount(JArray probes, int expected)
        {
            Assert.IsNotNull(probes, "缺少 probes");
            Assert.AreEqual(expected, probes.Count, $"探针点数应为 {expected}（0/0.25/0.5/0.75/1.0）");
        }

        private static double HorizontalDistance(JObject a, JObject b)
        {
            double dx = (double)a["x"] - (double)b["x"];
            double dz = (double)a["z"] - (double)b["z"];
            return Math.Sqrt(dx * dx + dz * dz);
        }

        private static void AssertNear(double expected, double actual, double tolerance, string message)
        {
            Assert.IsTrue(
                Math.Abs(expected - actual) <= tolerance,
                $"{message}：期望 {expected:F3}，实际 {actual:F3}，容差 {tolerance}");
        }
    }
}
