using Moq;
using Xunit;
using XplorePlane.Models;
using XplorePlane.Services;
using XplorePlane.ViewModels;
using XplorePlane.Tests.Helpers;

namespace XplorePlane.Tests.Pipeline
{
    /// <summary>
    /// 测试 PipelineEditorViewModel 的命令逻辑
    /// 覆盖任务 6.1 / 6.2 / 6.3 / 6.4 / 6.8
    /// </summary>
    public class PipelineEditorViewModelTests
    {
        private readonly Mock<IImageProcessingService> _mockImageSvc;
        private readonly Mock<IPipelineExecutionService> _mockExecSvc;
        private readonly Mock<IPipelinePersistenceService> _mockPersistSvc;

        public PipelineEditorViewModelTests()
        {
            _mockImageSvc = TestHelpers.CreateMockImageService();
            _mockExecSvc = new Mock<IPipelineExecutionService>();
            _mockPersistSvc = new Mock<IPipelinePersistenceService>();
        }

        private PipelineEditorViewModel CreateVm() =>
            new PipelineEditorViewModel(_mockImageSvc.Object, _mockExecSvc.Object, _mockPersistSvc.Object);

        // ── 6.1 AddOperatorCommand ────────────────────────────────────

        [Fact]
        public void AddOperator_ValidKey_AppendsNode()
        {
            var vm = CreateVm();
            vm.AddOperatorCommand.Execute("Blur");

            Assert.Single(vm.PipelineNodes);
            Assert.Equal("Blur", vm.PipelineNodes[0].OperatorKey);
            Assert.Equal(0, vm.PipelineNodes[0].Order);
        }

        [Fact]
        public void AddOperator_InvalidKey_NodeNotAdded()
        {
            var vm = CreateVm();
            vm.AddOperatorCommand.Execute("NonExistentKey");

            Assert.Empty(vm.PipelineNodes);
            Assert.Contains("未注册", vm.StatusMessage);
        }

        [Fact]
        public void AddOperator_NullKey_NodeNotAdded()
        {
            var vm = CreateVm();
            vm.AddOperatorCommand.Execute(null);

            Assert.Empty(vm.PipelineNodes);
        }

        [Fact]
        public void AddOperator_ExceedsMaxLength_NodeNotAdded()
        {
            // 主项目中 MaxPipelineLength = 20，通过反射读取或直接添加 20 个
            var vm = CreateVm();
            for (int i = 0; i < 20; i++)
                vm.AddOperatorCommand.Execute("Blur");

            Assert.Equal(20, vm.PipelineNodes.Count);

            vm.AddOperatorCommand.Execute("Blur");

            Assert.Equal(20, vm.PipelineNodes.Count);
            Assert.Contains("上限", vm.StatusMessage);
        }

        [Fact]
        public void AddOperator_MultipleNodes_OrderIsSequential()
        {
            var vm = CreateVm();
            vm.AddOperatorCommand.Execute("Blur");
            vm.AddOperatorCommand.Execute("Sharpen");
            vm.AddOperatorCommand.Execute("Threshold");

            Assert.Equal(3, vm.PipelineNodes.Count);
            for (int i = 0; i < 3; i++)
                Assert.Equal(i, vm.PipelineNodes[i].Order);
        }

        // ── 6.2 RemoveOperatorCommand ─────────────────────────────────

        [Fact]
        public void RemoveOperator_ExistingNode_NodeRemoved()
        {
            var vm = CreateVm();
            vm.AddOperatorCommand.Execute("Blur");
            var node = vm.PipelineNodes[0];

            vm.RemoveOperatorCommand.Execute(node);

            Assert.Empty(vm.PipelineNodes);
        }

        [Fact]
        public void RemoveOperator_MiddleNode_OrderRenumbered()
        {
            var vm = CreateVm();
            vm.AddOperatorCommand.Execute("Blur");
            vm.AddOperatorCommand.Execute("Sharpen");
            vm.AddOperatorCommand.Execute("Threshold");
            var middle = vm.PipelineNodes[1];

            vm.RemoveOperatorCommand.Execute(middle);

            Assert.Equal(2, vm.PipelineNodes.Count);
            Assert.Equal(0, vm.PipelineNodes[0].Order);
            Assert.Equal(1, vm.PipelineNodes[1].Order);
        }

        [Fact]
        public void RemoveOperator_SelectedNode_SelectedNodeCleared()
        {
            var vm = CreateVm();
            vm.AddOperatorCommand.Execute("Blur");
            var node = vm.PipelineNodes[0];
            vm.SelectedNode = node;

            vm.RemoveOperatorCommand.Execute(node);

            Assert.Null(vm.SelectedNode);
        }

        [Fact]
        public void RemoveOperator_NullNode_NoException()
        {
            var vm = CreateVm();
            var ex = Record.Exception(() => vm.RemoveOperatorCommand.Execute(null));
            Assert.Null(ex);
        }

        // ── 6.3 ReorderOperatorCommand ────────────────────────────────

        [Fact]
        public void ReorderOperator_MoveFirstToLast_OrderConsistent()
        {
            var vm = CreateVm();
            vm.AddOperatorCommand.Execute("Blur");
            vm.AddOperatorCommand.Execute("Sharpen");
            vm.AddOperatorCommand.Execute("Threshold");

            vm.ReorderOperatorCommand.Execute(new PipelineReorderArgs { OldIndex = 0, NewIndex = 2 });

            for (int i = 0; i < vm.PipelineNodes.Count; i++)
                Assert.Equal(i, vm.PipelineNodes[i].Order);
        }

        [Fact]
        public void ReorderOperator_MoveLastToFirst_OrderConsistent()
        {
            var vm = CreateVm();
            vm.AddOperatorCommand.Execute("Blur");
            vm.AddOperatorCommand.Execute("Sharpen");
            vm.AddOperatorCommand.Execute("Threshold");
            var lastName = vm.PipelineNodes[2].OperatorKey;

            vm.ReorderOperatorCommand.Execute(new PipelineReorderArgs { OldIndex = 2, NewIndex = 0 });

            Assert.Equal(lastName, vm.PipelineNodes[0].OperatorKey);
            for (int i = 0; i < vm.PipelineNodes.Count; i++)
                Assert.Equal(i, vm.PipelineNodes[i].Order);
        }

        [Fact]
        public void ReorderOperator_SameIndex_NoChange()
        {
            var vm = CreateVm();
            vm.AddOperatorCommand.Execute("Blur");
            vm.AddOperatorCommand.Execute("Sharpen");
            var originalFirst = vm.PipelineNodes[0].OperatorKey;

            vm.ReorderOperatorCommand.Execute(new PipelineReorderArgs { OldIndex = 0, NewIndex = 0 });

            Assert.Equal(originalFirst, vm.PipelineNodes[0].OperatorKey);
        }

        [Fact]
        public void ReorderOperator_OutOfBoundsIndex_NoException()
        {
            var vm = CreateVm();
            vm.AddOperatorCommand.Execute("Blur");

            var ex = Record.Exception(() =>
                vm.ReorderOperatorCommand.Execute(new PipelineReorderArgs { OldIndex = 0, NewIndex = 99 }));
            Assert.Null(ex);
        }

        // ── 6.4 LoadNodeParameters ────────────────────────────────────

        [Fact]
        public void LoadNodeParameters_ModifiedValuePreserved()
        {
            var param = TestHelpers.MakeIntParam("Radius", value: 5, min: 1, max: 20);
            _mockImageSvc.Setup(s => s.GetProcessorParameters("Blur"))
                .Returns(new[] { param });

            var vm = CreateVm();
            vm.AddOperatorCommand.Execute("Blur");
            var node = vm.PipelineNodes[0];

            // 修改参数值
            node.Parameters[0].Value = 10;

            // 重新选中节点触发 LoadNodeParameters
            vm.SelectedNode = null;
            vm.SelectedNode = node;

            Assert.Equal(10, node.Parameters[0].Value);
        }

        [Fact]
        public void LoadNodeParameters_UnmodifiedUsesDefault()
        {
            var param = TestHelpers.MakeIntParam("Radius", value: 5, min: 1, max: 20);
            _mockImageSvc.Setup(s => s.GetProcessorParameters("Blur"))
                .Returns(new[] { param });

            var vm = CreateVm();
            vm.AddOperatorCommand.Execute("Blur");
            var node = vm.PipelineNodes[0];

            Assert.Equal(5, node.Parameters[0].Value);
        }

        // ── 6.8 参数范围校验 ──────────────────────────────────────────

        [Fact]
        public void ParameterValidation_ValueWithinRange_IsValid()
        {
            var param = TestHelpers.MakeIntParam("Radius", value: 5, min: 1, max: 10);
            var vm = new ProcessorParameterVM(param) { Value = 7 };

            Assert.True(vm.IsValueValid);
        }

        [Fact]
        public void ParameterValidation_ValueBelowMin_IsInvalid()
        {
            var param = TestHelpers.MakeIntParam("Radius", value: 5, min: 1, max: 10);
            var vm = new ProcessorParameterVM(param) { Value = 0 };

            Assert.False(vm.IsValueValid);
        }

        [Fact]
        public void ParameterValidation_ValueAboveMax_IsInvalid()
        {
            var param = TestHelpers.MakeIntParam("Radius", value: 5, min: 1, max: 10);
            var vm = new ProcessorParameterVM(param) { Value = 11 };

            Assert.False(vm.IsValueValid);
        }

        [Fact]
        public void ParameterValidation_BoundaryValues_AreValid()
        {
            var param = TestHelpers.MakeIntParam("Radius", value: 5, min: 1, max: 10);
            var vmMin = new ProcessorParameterVM(param) { Value = 1 };
            var vmMax = new ProcessorParameterVM(param) { Value = 10 };

            Assert.True(vmMin.IsValueValid);
            Assert.True(vmMax.IsValueValid);
        }
    }
}
