using Microsoft.VisualStudio.TestTools.UnitTesting;
using NavisworksTransport.Utils.CoordinateSystem;
using System;
using System.Numerics;

namespace NavisworksTransport.UnitTests.CoordinateSystem
{
    [TestClass]
    public class ObjectPassageProjectionOptimizerTests
    {
        [TestMethod]
        public void Optimize_LongTallBox_ShouldFindThreeAxisPoseBetterThanYawOnly()
        {
            var request = CreateZUpRequest(
                sizeX: 8.0,
                sizeY: 4.0,
                sizeZ: 2.0,
                pathForward: Vector3.UnitX);

            ObjectPassageProjectionOptimizationResult result = ObjectPassageProjectionOptimizer.Optimize(request);
            ObjectPassageProjectionScore yawOnly = ObjectPassageProjectionOptimizer.Evaluate(
                request,
                new LocalEulerRotationCorrection(0.0, 0.0, 90.0));

            Assert.IsTrue(result.Success, result.ErrorMessage);
            Assert.IsTrue(result.Score.Area < yawOnly.Area - 1e-6,
                $"三轴优化面积 {result.Score.Area:F6} 应小于单轴 yaw 面积 {yawOnly.Area:F6}");
            Assert.AreEqual(4.0, result.Score.WidthAcrossPath, 1e-4);
            Assert.AreEqual(2.0, result.Score.HeightAlongHostUp, 1e-4);
        }

        [TestMethod]
        public void Optimize_WhenAreasAreClose_ShouldPreferLowerHeight()
        {
            var request = CreateZUpRequest(
                sizeX: 6.0,
                sizeY: 3.0,
                sizeZ: 2.0,
                pathForward: Vector3.UnitX,
                areaRelativeTieTolerance: 0.05);

            ObjectPassageProjectionOptimizationResult result = ObjectPassageProjectionOptimizer.Optimize(request);

            Assert.IsTrue(result.Success, result.ErrorMessage);
            Assert.AreEqual(3.0, result.Score.WidthAcrossPath, 1e-4);
            Assert.AreEqual(2.0, result.Score.HeightAlongHostUp, 1e-4);
        }

        [TestMethod]
        public void Optimize_WhenBaselineAlreadyAlignedToPath_ShouldKeepZeroCorrection()
        {
            Quaternion baselineRotation = Quaternion.CreateFromAxisAngle(
                Vector3.UnitZ,
                (float)(Math.PI / 4.0));
            var request = new ObjectPassageProjectionOptimizationRequest(
                sizeX: 6.0,
                sizeY: 3.0,
                sizeZ: 2.0,
                hostPathForward: Vector3.Normalize(new Vector3(1.0f, 1.0f, 0.0f)),
                hostUp: Vector3.UnitZ,
                areaRelativeTieTolerance: 0.01,
                baselineHostRotation: baselineRotation);

            ObjectPassageProjectionOptimizationResult result = ObjectPassageProjectionOptimizer.Optimize(request);

            Assert.IsTrue(result.Success, result.ErrorMessage);
            Assert.AreEqual(0.0, result.Correction.XDegrees, 1e-6);
            Assert.AreEqual(0.0, result.Correction.YDegrees, 1e-6);
            Assert.AreEqual(0.0, result.Correction.ZDegrees, 1e-6);
            Assert.AreEqual(3.0, result.Score.WidthAcrossPath, 1e-4);
            Assert.AreEqual(2.0, result.Score.HeightAlongHostUp, 1e-4);
        }

        [TestMethod]
        public void Evaluate_YUp_ShouldUseHostYAsHeight()
        {
            var request = new ObjectPassageProjectionOptimizationRequest(
                sizeX: 6.0,
                sizeY: 2.0,
                sizeZ: 4.0,
                hostPathForward: Vector3.UnitX,
                hostUp: Vector3.UnitY);

            ObjectPassageProjectionScore score = ObjectPassageProjectionOptimizer.Evaluate(
                request,
                LocalEulerRotationCorrection.Zero);

            Assert.AreEqual(4.0, score.WidthAcrossPath, 1e-6);
            Assert.AreEqual(2.0, score.HeightAlongHostUp, 1e-6);
            Assert.AreEqual(8.0, score.Area, 1e-6);
        }

        [TestMethod]
        public void Optimize_SameInputTwice_ShouldReturnSameCorrection()
        {
            var request = CreateZUpRequest(
                sizeX: 9.0,
                sizeY: 4.0,
                sizeZ: 2.0,
                pathForward: Vector3.Normalize(new Vector3(1.0f, 1.0f, 0.0f)));

            ObjectPassageProjectionOptimizationResult first = ObjectPassageProjectionOptimizer.Optimize(request);
            ObjectPassageProjectionOptimizationResult second = ObjectPassageProjectionOptimizer.Optimize(request);

            Assert.IsTrue(first.Success, first.ErrorMessage);
            Assert.IsTrue(second.Success, second.ErrorMessage);
            Assert.AreEqual(first.Correction.XDegrees, second.Correction.XDegrees, 1e-9);
            Assert.AreEqual(first.Correction.YDegrees, second.Correction.YDegrees, 1e-9);
            Assert.AreEqual(first.Correction.ZDegrees, second.Correction.ZDegrees, 1e-9);
            Assert.AreEqual(first.Score.Area, second.Score.Area, 1e-9);
        }

        [TestMethod]
        public void Optimize_PathForwardParallelToHostUp_ShouldFail()
        {
            var request = CreateZUpRequest(
                sizeX: 6.0,
                sizeY: 4.0,
                sizeZ: 2.0,
                pathForward: Vector3.UnitZ);

            ObjectPassageProjectionOptimizationResult result = ObjectPassageProjectionOptimizer.Optimize(request);

            Assert.IsFalse(result.Success);
            StringAssert.Contains(result.ErrorMessage, "路径方向退化");
        }

        private static ObjectPassageProjectionOptimizationRequest CreateZUpRequest(
            double sizeX,
            double sizeY,
            double sizeZ,
            Vector3 pathForward,
            double areaRelativeTieTolerance = 0.01)
        {
            return new ObjectPassageProjectionOptimizationRequest(
                sizeX,
                sizeY,
                sizeZ,
                pathForward,
                Vector3.UnitZ,
                areaRelativeTieTolerance);
        }
    }
}
