using Xunit;
using XplorePlane.Models;
using XplorePlane.Services;
using XplorePlane.Tests.Helpers;
using System.Threading.Tasks;
using System.IO;
using System;

namespace XplorePlane.Tests.Pipeline
{
    /// <summary>
    /// 测试 PipelinePersistenceService（任务 6.5）
    /// </summary>
    public class PipelinePersistenceServiceTests : IDisposable
    {
        private readonly string _tempDir;
        private readonly PipelinePersistenceService _svc;

        public PipelinePersistenceServiceTests()
        {
            _tempDir = Path.Combine(Path.GetTempPath(), $"XplorePlaneTests_{Guid.NewGuid():N}");
            Directory.CreateDirectory(_tempDir);

            var mockImageSvc = TestHelpers.CreateMockImageService(new[] { "Blur", "Sharpen" });
            _svc = new PipelinePersistenceService(mockImageSvc.Object, _tempDir);
        }

        public void Dispose()
        {
            if (Directory.Exists(_tempDir))
                Directory.Delete(_tempDir, recursive: true);
        }

        private static PipelineModel BuildModel(string name = "TestPipeline", params string[] keys)
        {
            var model = new PipelineModel { Name = name };
            for (int i = 0; i < keys.Length; i++)
                model.Nodes.Add(new PipelineNodeModel { OperatorKey = keys[i], Order = i });
            return model;
        }

        // ── 往返一致性 ────────────────────────────────────────────────

        [Fact]
        public async Task SaveAndLoad_RoundTrip_NamePreserved()
        {
            var model = BuildModel("MyPipeline", "Blur", "Sharpen");
            var path = Path.Combine(_tempDir, "test.pipeline.json");

            await _svc.SaveAsync(model, path);
            var loaded = await _svc.LoadAsync(path);

            Assert.Equal("MyPipeline", loaded.Name);
        }

        [Fact]
        public async Task SaveAndLoad_RoundTrip_NodeCountPreserved()
        {
            var model = BuildModel("P", "Blur", "Sharpen");
            var path = Path.Combine(_tempDir, "test.pipeline.json");

            await _svc.SaveAsync(model, path);
            var loaded = await _svc.LoadAsync(path);

            Assert.Equal(2, loaded.Nodes.Count);
        }

        [Fact]
        public async Task SaveAndLoad_RoundTrip_OperatorKeysPreserved()
        {
            var model = BuildModel("P", "Blur", "Sharpen");
            var path = Path.Combine(_tempDir, "test.pipeline.json");

            await _svc.SaveAsync(model, path);
            var loaded = await _svc.LoadAsync(path);

            Assert.Equal("Blur", loaded.Nodes[0].OperatorKey);
            Assert.Equal("Sharpen", loaded.Nodes[1].OperatorKey);
        }

        [Fact]
        public async Task SaveAndLoad_RoundTrip_OrderIsSequential()
        {
            var model = BuildModel("P", "Blur", "Sharpen");
            var path = Path.Combine(_tempDir, "test.pipeline.json");

            await _svc.SaveAsync(model, path);
            var loaded = await _svc.LoadAsync(path);

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

        // ── 非法路径被拒绝 ────────────────────────────────────────────

        [Fact]
        public async Task SaveAsync_PathTraversal_ThrowsUnauthorized()
        {
            var model = BuildModel("P");
            await Assert.ThrowsAsync<UnauthorizedAccessException>(
                () => _svc.SaveAsync(model, "../../../evil.json"));
        }

        [Fact]
        public async Task LoadAsync_PathTraversal_ThrowsUnauthorized()
        {
            await Assert.ThrowsAsync<UnauthorizedAccessException>(
                () => _svc.LoadAsync("../../etc/passwd"));
        }

        [Fact]
        public async Task LoadAsync_EmptyPath_ThrowsArgumentException()
        {
            await Assert.ThrowsAsync<ArgumentException>(
                () => _svc.LoadAsync(string.Empty));
        }

        [Fact]
        public async Task LoadAsync_NullPath_ThrowsArgumentException()
        {
            await Assert.ThrowsAsync<ArgumentException>(
                () => _svc.LoadAsync(null!));
        }

        [Fact]
        public async Task LoadAsync_FileNotFound_ThrowsFileNotFoundException()
        {
            var path = Path.Combine(_tempDir, "nonexistent.pipeline.json");
            await Assert.ThrowsAsync<FileNotFoundException>(
                () => _svc.LoadAsync(path));
        }

        // ── 非法 OperatorKey 被跳过 ───────────────────────────────────

        [Fact]
        public async Task Load_UnregisteredOperatorKey_NodeSkipped()
        {
            // 手动写入包含未注册 Key 的 JSON
            var json = """
                {
                  "Id": "00000000-0000-0000-0000-000000000001",
                  "Name": "Test",
                  "DeviceId": "",
                  "Nodes": [
                    { "Id": "00000000-0000-0000-0000-000000000002", "OperatorKey": "Blur", "Order": 0, "IsEnabled": true, "Parameters": {} },
                    { "Id": "00000000-0000-0000-0000-000000000003", "OperatorKey": "UNKNOWN_KEY", "Order": 1, "IsEnabled": true, "Parameters": {} }
                  ]
                }
                """;
            var path = Path.Combine(_tempDir, "mixed.pipeline.json");
            await File.WriteAllTextAsync(path, json);

            var loaded = await _svc.LoadAsync(path);

            Assert.Single(loaded.Nodes);
            Assert.Equal("Blur", loaded.Nodes[0].OperatorKey);
        }

        [Fact]
        public async Task Load_AllUnregisteredKeys_ReturnsEmptyNodes()
        {
            var json = """
                {
                  "Id": "00000000-0000-0000-0000-000000000001",
                  "Name": "Test",
                  "DeviceId": "",
                  "Nodes": [
                    { "Id": "00000000-0000-0000-0000-000000000002", "OperatorKey": "FAKE1", "Order": 0, "IsEnabled": true, "Parameters": {} },
                    { "Id": "00000000-0000-0000-0000-000000000003", "OperatorKey": "FAKE2", "Order": 1, "IsEnabled": true, "Parameters": {} }
                  ]
                }
                """;
            var path = Path.Combine(_tempDir, "all_fake.pipeline.json");
            await File.WriteAllTextAsync(path, json);

            var loaded = await _svc.LoadAsync(path);

            Assert.Empty(loaded.Nodes);
        }

        // ── LoadAllAsync ──────────────────────────────────────────────

        [Fact]
        public async Task LoadAllAsync_EmptyDirectory_ReturnsEmpty()
        {
            var result = await _svc.LoadAllAsync(_tempDir);
            Assert.Empty(result);
        }

        [Fact]
        public async Task LoadAllAsync_MultipleFiles_ReturnsAll()
        {
            var m1 = BuildModel("P1", "Blur");
            var m2 = BuildModel("P2", "Sharpen");
            await _svc.SaveAsync(m1, Path.Combine(_tempDir, "p1.pipeline.json"));
            await _svc.SaveAsync(m2, Path.Combine(_tempDir, "p2.pipeline.json"));

            var result = await _svc.LoadAllAsync(_tempDir);

            Assert.Equal(2, result.Count);
        }
    }
}
