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

namespace NavisworksTransport.UnitTests.Integration
{
    /// <summary>
    /// 路径导入与有效性集成测试。
    /// 验证完整流程：JSON/XML 导入 → Rail 属性映射 → 长度计算 → 切换路径 → 完整动画/碰撞流程。
    ///
    /// 背景：原单测 PathPersistenceTests 验证 Rail 属性映射时，导入必经 RecalculateRoute
    /// 触发 Rail 长度计算（读取 Navisworks Point3D，要求 API 初始化），而单元测试进程不是
    /// Navisworks 宿主无法初始化 API，导致长度计算必然失败（异常被容错吞掉，测试“假通过”）。
    /// 按“依赖 Navisworks 的验证归入集成测试”原则，改为在本测试中于 Navisworks 进程内
    /// 完整验证导入链路（含单测无法覆盖的长度计算正确性），单测中的对应测试已删除。
    /// </summary>
    [TestClass]
    [TestCategory("NavisworksIntegration")]
    public class PathImportValidityAutomationTests
    {
        private const int TestFrameRate = 15;
        private const double TestDurationSeconds = 5.0;

        // 模型内已验证可用的 Rail 坐标（与 resources/auto-test-routes.xml 自动测试_Rail 同源）
        private const double StartX = -207.591;
        private const double StartY = 29.482;
        private const double StartZ = 3.504;
        private const double EndX = -195.556;
        private const double EndY = 27.776;
        private const double EndZ = 3.308;

        private const double RailNormalOffset = 0.25;

        // 输入两点坐标的模型单位直线距离（服务端按文档单位换算为米）
        private static readonly double ExpectedLengthModelUnits = Math.Sqrt(
            (EndX - StartX) * (EndX - StartX) +
            (EndY - StartY) * (EndY - StartY) +
            (EndZ - StartZ) * (EndZ - StartZ));

        [TestMethod]
        [Timeout(360000)]
        public async Task ImportRailRoute_FromJson_PropertiesLengthAndAnimation_AreValid()
        {
            await RunImportValidityFlowAsync("集成测试_导入_Rail_JSON", "rail-import.json", BuildRailJson).ConfigureAwait(false);
        }

        [TestMethod]
        [Timeout(360000)]
        public async Task ImportRailRoute_FromXml_PropertiesLengthAndAnimation_AreValid()
        {
            await RunImportValidityFlowAsync("集成测试_导入_Rail_XML", "rail-import.xml", BuildRailXml).ConfigureAwait(false);
        }

        private static async Task RunImportValidityFlowAsync(
            string routeName,
            string fileName,
            Func<string, string, string, string, string> buildContent)
        {
            using (var client = new NavisworksTestAutomationClient())
            {
                await client.EnsureServiceReadyAsync(TimeSpan.FromSeconds(90)).ConfigureAwait(false);

                string tempDir = CreateTempDir();
                try
                {
                    string filePath = Path.Combine(tempDir, fileName);
                    // PathPoints.Id 是全局主键：路径点 Id 必须随机，避免与历史残留/其他测试文件冲突（p1/p2 固定会主键冲突）
                    string routeId = "import-" + Guid.NewGuid().ToString("N");
                    string point1Id = Guid.NewGuid().ToString("N");
                    string point2Id = Guid.NewGuid().ToString("N");
                    File.WriteAllText(filePath, buildContent(routeName, routeId, point1Id, point2Id), new UTF8Encoding(false));

                    // 1. 导入（在 Navisworks 进程内执行 PathDataManager 导入，覆盖模式保证可重复执行）
                    JObject importResponse = await client.ImportRouteFileAsync(filePath, overwrite: true).ConfigureAwait(false);
                    AssertImportResult(importResponse, routeName);

                    // 2. 切换导入路径为当前路径
                    JObject selectResponse = await client.SelectRouteAsync(routeName).ConfigureAwait(false);
                    Assert.IsTrue(selectResponse.Value<bool>("ok"), "切换导入路径失败: " + (string)selectResponse["error"]);
                    Assert.AreEqual(routeName, (string)((JObject)selectResponse["data"])["selected"]["name"], "切换的路径不匹配");

                    // 3. 完整动画/碰撞流程验证路径有效性
                    JObject animationResponse = await client.RunCollisionTestAsync(
                        "Rail",
                        240,
                        routeName: routeName,
                        frameRate: TestFrameRate,
                        durationSeconds: TestDurationSeconds).ConfigureAwait(false);
                    Assert.IsTrue(animationResponse.Value<bool>("ok"), "导入路径动画流程失败: " + (string)animationResponse["error"]);

                    JObject data = (JObject)animationResponse["data"];
                    JObject route = (JObject)data["route"];
                    Assert.AreEqual(routeName, (string)route["name"], "动画流程使用的路径不匹配");
                    JObject animation = (JObject)data["animation"];
                    Assert.AreEqual("Finished", (string)animation["currentState"], "动画未完成");
                    Assert.IsTrue((int)animation["totalFrames"] > 0, "动画总帧数应大于 0");
                }
                finally
                {
                    SafeDelete(tempDir);
                }
            }
        }

        private static void AssertImportResult(JObject response, string expectedRouteName)
        {
            Assert.IsTrue(response.Value<bool>("ok"), "导入失败: " + (string)response["error"]);

            JObject data = (JObject)response["data"];
            Assert.IsNotNull(data, "缺少导入结果 data");

            // 服务端返回的米→模型单位因子，用于把输入坐标距离换算为期望的米长度（不硬编码文档单位）
            double metersToModelUnits = data.Value<double?>("metersToModelUnits") ?? 1.0;
            double expectedLengthMeters = ExpectedLengthModelUnits / metersToModelUnits;

            JArray routes = (JArray)data["routes"];
            Assert.IsNotNull(routes, "缺少 routes");
            Assert.AreEqual(1, routes.Count, "应导入 1 条路径");

            JObject route = (JObject)routes[0];
            Assert.AreEqual(expectedRouteName, (string)route["name"], "导入路径名称不匹配");
            Assert.AreEqual("Rail", (string)route["pathType"], "导入路径类型不匹配");
            Assert.AreEqual(2, (int)route["pointCount"], "导入路径点数不匹配");
            Assert.AreEqual("UnderRail", (string)route["railMountMode"], "railMountMode 映射不匹配");
            Assert.AreEqual("RailCenterLine", (string)route["railPathDefinitionMode"], "railPathDefinitionMode 映射不匹配");
            Assert.AreEqual(RailNormalOffset, (double)route["railNormalOffset"], 1e-6, "railNormalOffset 映射不匹配");
            Assert.AreEqual(expectedLengthMeters, (double)route["totalLengthInMeters"], 1e-3, "导入路径长度计算不匹配");

            JObject normal = (JObject)route["railPreferredNormal"];
            Assert.IsNotNull(normal, "缺少 railPreferredNormal");
            Assert.AreEqual(-0.2, (double)normal["x"], 1e-6, "railPreferredNormal.x 映射不匹配");
            Assert.AreEqual(0.5, (double)normal["y"], 1e-6, "railPreferredNormal.y 映射不匹配");
            Assert.AreEqual(0.8, (double)normal["z"], 1e-6, "railPreferredNormal.z 映射不匹配");
        }

        private static string BuildRailJson(string routeName, string routeId, string point1Id, string point2Id)
        {
            return string.Format(
                System.Globalization.CultureInfo.InvariantCulture,
                @"{{
  ""PathPlanningData"": {{
    ""version"": ""1.0"",
    ""generator"": ""integration-test"",
    ""timestamp"": ""2026-08-02T21:30:00"",
    ""ProjectInfo"": {{ ""name"": ""test"", ""description"": ""test"", ""units"": ""meters"", ""coordinateSystem"": ""Global"" }},
    ""Routes"": [
      {{
        ""id"": ""{0}"",
        ""name"": ""{1}"",
        ""description"": """",
        ""pathType"": ""Rail"",
        ""railMountMode"": ""UnderRail"",
        ""railPathDefinitionMode"": ""RailCenterLine"",
        ""railNormalOffset"": 0.25,
        ""railPreferredNormal"": {{ ""x"": -0.2, ""y"": 0.5, ""z"": 0.8 }},
        ""totalLength"": 10.0,
        ""objectLimits"": {{ ""maxLength"": 0, ""maxWidth"": 0, ""maxHeight"": 0, ""safetyMargin"": 0 }},
        ""gridSize"": 1.0,
        ""liftHeight"": 0.0,
        ""created"": ""2026-08-02T21:30:00"",
        ""points"": [
          {{ ""id"": ""{2}"", ""name"": ""start"", ""type"": ""StartPoint"", ""index"": 0, ""x"": {4}, ""y"": {5}, ""z"": {6}, ""created"": ""2026-08-02T21:30:00"" }},
          {{ ""id"": ""{3}"", ""name"": ""end"", ""type"": ""EndPoint"", ""index"": 1, ""x"": {7}, ""y"": {8}, ""z"": {9}, ""created"": ""2026-08-02T21:30:00"" }}
        ]
      }}
    ]
  }}
}}",
                routeId,
                routeName,
                point1Id,
                point2Id,
                StartX, StartY, StartZ,
                EndX, EndY, EndZ);
        }

        private static string BuildRailXml(string routeName, string routeId, string point1Id, string point2Id)
        {
            return string.Format(
                System.Globalization.CultureInfo.InvariantCulture,
                @"<?xml version=""1.0"" encoding=""UTF-8""?>
<PathPlanningData xmlns=""http://www.3ds.com/delmia/pathplanning"" version=""1.0"" generator=""integration-test"" timestamp=""2026-08-02T21:30:00"">
  <ProjectInfo name=""test"" description=""test"" units=""meters"" coordinateSystem=""Global"" />
  <Routes>
    <Route id=""{0}"" name=""{1}"" description="""" pathType=""Rail"" railMountMode=""UnderRail"" railPathDefinitionMode=""RailCenterLine"" railNormalOffset=""0.25"" railPreferredNormalX=""-0.2"" railPreferredNormalY=""0.5"" railPreferredNormalZ=""0.8"" totalLength=""10.0"" maxObjectLength=""0.0"" maxObjectWidth=""0.0"" maxObjectHeight=""0.0"" safetyMargin=""0.0"" gridSize=""1.0"" liftHeight=""0.0"" created=""2026-08-02T21:30:00"">
      <Points>
        <Point id=""{2}"" name=""start"" type=""StartPoint"" index=""0"" x=""{4}"" y=""{5}"" z=""{6}"" created=""2026-08-02T21:30:00"" />
        <Point id=""{3}"" name=""end"" type=""EndPoint"" index=""1"" x=""{7}"" y=""{8}"" z=""{9}"" created=""2026-08-02T21:30:00"" />
      </Points>
    </Route>
  </Routes>
</PathPlanningData>",
                routeId,
                routeName,
                point1Id,
                point2Id,
                StartX, StartY, StartZ,
                EndX, EndY, EndZ);
        }

        private static string CreateTempDir()
        {
            string tempDir = Path.Combine(Path.GetTempPath(), "NavisworksTransportIntegrationTests", Guid.NewGuid().ToString("N"));
            Directory.CreateDirectory(tempDir);
            return tempDir;
        }

        private static void SafeDelete(string directory)
        {
            if (!Directory.Exists(directory))
            {
                return;
            }

            try
            {
                Directory.Delete(directory, true);
            }
            catch
            {
            }
        }

    }
}
