using Flyshot.Core.Config;

namespace Flyshot.Core.Tests;

/// <summary>
/// 锁定 Task 3 的兼容输入行为，确保旧配置、JSON 模型元数据和路径策略都能被稳定加载。
/// </summary>
public sealed class ConfigCompatibilityTests
{
    /// <summary>
    /// 验证现有旧样本配置能被加载，并保持关键机器人参数与飞拍程序内容不变。
    /// </summary>
    [Fact]
    public void RobotConfigLoader_LoadsLegacyRobotConfig_AndPreservesPrograms()
    {
        var workspaceRoot = GetWorkspaceRoot();
        var configPath = Path.Combine(workspaceRoot, "Rvbust", "EOL10_EAU_0", "EOL10_EAU_0.json");

        var loaded = new RobotConfigLoader().Load(configPath);

        Assert.True(loaded.Robot.UseDo);
        Assert.Equal([7, 8], loaded.Robot.IoAddresses);
        Assert.Equal(2, loaded.Robot.IoKeepCycles);
        Assert.Equal(7, loaded.Robot.TriggerSampleIndexOffsetCycles);
        Assert.Equal(1.0, loaded.Robot.AccLimitScale);
        Assert.Equal(1.0, loaded.Robot.JerkLimitScale);
        Assert.Equal(1.0, loaded.Robot.PlanningSpeedScale);
        Assert.True(loaded.Robot.SmoothStartStopTiming);
        Assert.Equal(5, loaded.Robot.AdaptIcspTryNum);

        var program = Assert.Contains("EOL10_EAU_0", loaded.Programs);
        Assert.Equal("EOL10_EAU_0", program.Name);
        Assert.Equal(45, program.Waypoints.Count);
        Assert.Equal(42, program.ShotWaypointCount);
        Assert.Empty(program.AddressGroups[0].Addresses);
        Assert.Equal([4, 3], program.AddressGroups[1].Addresses);
    }

    /// <summary>
    /// 验证旧配置缺少 offset_values 和 addr 字段时，会自动回填与旧系统一致的默认值。
    /// </summary>
    [Fact]
    public void RobotConfigLoader_FillsLegacyDefaults_WhenOptionalFieldsAreMissing()
    {
        var tempRoot = CreateTempDirectory();
        try
        {
            var configPath = Path.Combine(tempRoot, "legacy.json");
            File.WriteAllText(
                configPath,
                """
                {
                  "robot": {
                    "use_do": false,
                    "io_keep_cycles": 3,
                    "acc_limit": 0.5,
                    "jerk_limit": 0.25
                  },
                  "flying_shots": {
                    "demo": {
                      "traj_waypoints": [[0, 1], [2, 3], [4, 5]],
                      "shot_flags": [0, 1, 0]
                    }
                  }
                }
                """);

            var loaded = new RobotConfigLoader().Load(configPath);
            var program = Assert.Contains("demo", loaded.Programs);

            Assert.False(loaded.Robot.UseDo);
            Assert.Empty(loaded.Robot.IoAddresses);
            Assert.Equal(3, loaded.Robot.IoKeepCycles);
            Assert.Equal(7, loaded.Robot.TriggerSampleIndexOffsetCycles);
            Assert.Equal(0.5, loaded.Robot.AccLimitScale);
            Assert.Equal(0.25, loaded.Robot.JerkLimitScale);
            Assert.Equal(1.0, loaded.Robot.PlanningSpeedScale);
            Assert.Equal(1.0, loaded.Robot.SpeedRatio);
            Assert.True(loaded.Robot.SmoothStartStopTiming);
            Assert.Equal([0, 0, 0], program.OffsetValues);
            Assert.All(program.AddressGroups, group => Assert.Empty(group.Addresses));
        }
        finally
        {
            Directory.Delete(tempRoot, recursive: true);
        }
    }

    /// <summary>
    /// 验证 RobotConfig.json 可以显式配置运行时默认速度倍率，供 MoveJoint 和后续执行链路初始化使用。
    /// </summary>
    [Fact]
    public void RobotConfigLoader_LoadsRuntimeSpeedRatio()
    {
        var tempRoot = CreateTempDirectory();
        try
        {
            var configPath = Path.Combine(tempRoot, "legacy.json");
            File.WriteAllText(
                configPath,
                """
                {
                  "robot": {
                    "use_do": true,
                    "io_keep_cycles": 2,
                    "acc_limit": 1.0,
                    "jerk_limit": 1.0,
                    "speed_ratio": 0.65
                  },
                  "flying_shots": {
                    "demo": {
                      "traj_waypoints": [[0, 1], [2, 3], [4, 5], [6, 7]],
                      "shot_flags": [false, false, false, false]
                    }
                  }
                }
                """);

            var loaded = new RobotConfigLoader().Load(configPath);

            Assert.Equal(0.65, loaded.Robot.SpeedRatio, precision: 6);
        }
        finally
        {
            Directory.Delete(tempRoot, recursive: true);
        }
    }

    /// <summary>
    /// 验证 RobotConfig.json 可以显式配置规划限速倍率，且该倍率独立于运行时 J519 速度倍率。
    /// </summary>
    [Fact]
    public void RobotConfigLoader_LoadsPlanningSpeedScale()
    {
        var tempRoot = CreateTempDirectory();
        try
        {
            var configPath = Path.Combine(tempRoot, "legacy.json");
            File.WriteAllText(
                configPath,
                """
                {
                  "robot": {
                    "use_do": true,
                    "io_keep_cycles": 2,
                    "acc_limit": 1.0,
                    "jerk_limit": 1.0,
                    "planning_speed_scale": 0.742277
                  },
                  "flying_shots": {
                    "demo": {
                      "traj_waypoints": [[0, 1], [2, 3], [4, 5], [6, 7]],
                      "shot_flags": [false, false, false, false]
                    }
                  }
                }
                """);

            var loaded = new RobotConfigLoader().Load(configPath);

            Assert.Equal(0.742277, loaded.Robot.PlanningSpeedScale, precision: 6);
        }
        finally
        {
            Directory.Delete(tempRoot, recursive: true);
        }
    }

    /// <summary>
    /// 验证 RobotConfig.json 可以显式配置触发绑定后的命令 sample 后移周期数。
    /// </summary>
    [Fact]
    public void RobotConfigLoader_LoadsTriggerSampleIndexOffsetCycles()
    {
        var tempRoot = CreateTempDirectory();
        try
        {
            var configPath = Path.Combine(tempRoot, "legacy.json");
            File.WriteAllText(
                configPath,
                """
                {
                  "robot": {
                    "use_do": true,
                    "io_keep_cycles": 2,
                    "trigger_sample_index_offset_cycles": 8,
                    "acc_limit": 1.0,
                    "jerk_limit": 1.0
                  },
                  "flying_shots": {
                    "demo": {
                      "traj_waypoints": [[0, 1], [2, 3], [4, 5], [6, 7]],
                      "shot_flags": [false, false, false, false]
                    }
                  }
                }
                """);

            var loaded = new RobotConfigLoader().Load(configPath);

            Assert.Equal(8, loaded.Robot.TriggerSampleIndexOffsetCycles);
        }
        finally
        {
            Directory.Delete(tempRoot, recursive: true);
        }
    }

    /// <summary>
    /// 验证 RobotConfig.json 可以关闭飞拍执行前的二次平滑起停时间重映射。
    /// </summary>
    [Fact]
    public void RobotConfigLoader_LoadsSmoothStartStopTimingSwitch()
    {
        var tempRoot = CreateTempDirectory();
        try
        {
            var configPath = Path.Combine(tempRoot, "legacy.json");
            File.WriteAllText(
                configPath,
                """
                {
                  "robot": {
                    "use_do": true,
                    "io_keep_cycles": 2,
                    "acc_limit": 1.0,
                    "jerk_limit": 1.0,
                    "smooth_start_stop_timing": false
                  },
                  "flying_shots": {
                    "demo": {
                      "traj_waypoints": [[0, 1], [2, 3], [4, 5], [6, 7]],
                      "shot_flags": [false, false, false, false]
                    }
                  }
                }
                """);

            var loaded = new RobotConfigLoader().Load(configPath);

            Assert.False(loaded.Robot.SmoothStartStopTiming);
        }
        finally
        {
            Directory.Delete(tempRoot, recursive: true);
        }
    }

    /// <summary>
    /// 验证 JSON 模型解析会保留 Joint3 对 Joint2 的 couple 元数据，并构造规划侧可直接消费的 RobotProfile。
    /// </summary>
    [Fact]
    public void RobotModelLoader_LoadsRobotProfile_WithJointLimitsAndCoupling()
    {
        var replacementRoot = GetReplacementRoot();
        var modelPath = Path.Combine(replacementRoot, "Config", "Models", "LR_Mate_200iD_7L.json");

        var profile = new RobotModelLoader().LoadProfile(modelPath);

        Assert.Equal("FANUC_LR_Mate_200iD_7L", profile.Name);
        Assert.Equal(modelPath, profile.ModelPath);
        Assert.Equal(6, profile.DegreesOfFreedom);
        Assert.Equal(6.45, profile.JointLimits[0].VelocityLimit, precision: 2);
        Assert.Equal(29.81, profile.JointLimits[2].AccelerationLimit, precision: 2);

        var coupling = Assert.Single(profile.JointCouplings);
        Assert.Equal("Joint3", coupling.SlaveJointName);
        Assert.Equal("Joint2", coupling.MasterJointName);
        Assert.Equal(1.0, coupling.Multiplier);
        Assert.Equal(0.0, coupling.Offset);
    }

    /// <summary>
    /// 验证 RobotConfig 中的 acc_limit 和 jerk_limit 乘子会正确叠加到 JSON 模型关节限制上。
    /// </summary>
    [Fact]
    public void RobotModelLoader_AppliesAccelerationAndJerkScales()
    {
        var replacementRoot = GetReplacementRoot();
        var modelPath = Path.Combine(replacementRoot, "Config", "Models", "LR_Mate_200iD_7L.json");

        var profile = new RobotModelLoader().LoadProfile(modelPath, accLimitScale: 0.5, jerkLimitScale: 0.25);

        Assert.Equal(14.905, profile.JointLimits[2].AccelerationLimit, precision: 3);
        Assert.Equal(62.115, profile.JointLimits[2].JerkLimit, precision: 3);
    }

    /// <summary>
    /// 验证 JSON 模型可一次解析后同时生成规划约束视图和运动学几何视图。
    /// </summary>
    [Fact]
    public void RobotModelLoader_LoadsProfileAndKinematics_FromSingleParse()
    {
        var replacementRoot = GetReplacementRoot();
        var modelPath = Path.Combine(replacementRoot, "Config", "Models", "LR_Mate_200iD_7L.json");

        var loaded = new RobotModelLoader().LoadProfileAndKinematics(modelPath, accLimitScale: 0.5, jerkLimitScale: 0.25);

        Assert.Equal("FANUC_LR_Mate_200iD_7L", loaded.Profile.Name);
        Assert.Equal(modelPath, loaded.Profile.ModelPath);
        Assert.Equal(6, loaded.Profile.DegreesOfFreedom);
        Assert.Equal(14.905, loaded.Profile.JointLimits[2].AccelerationLimit, precision: 3);
        Assert.Equal(62.115, loaded.Profile.JointLimits[2].JerkLimit, precision: 3);

        Assert.Equal("FANUC_LR_Mate_200iD_7L", loaded.KinematicsModel.Name);
        Assert.True(loaded.KinematicsModel.Joints.Count >= loaded.Profile.DegreesOfFreedom);
        Assert.Contains(loaded.KinematicsModel.Joints, static joint => joint.Name == "Joint3" && joint.CoupleMaster == "Joint2");
    }

    /// <summary>
    /// 验证路径兼容层只从当前服务配置目录解析相对配置，并按平台策略生成默认用户数据目录。
    /// </summary>
    [Fact]
    public void PathCompatibility_ResolvesConfigDirectoryOnly_AndBuildsUserDataRoots()
    {
        var tempRoot = CreateTempDirectory();
        try
        {
            var configPath = Path.Combine(tempRoot, "Config", "sample.json");
            Directory.CreateDirectory(Path.GetDirectoryName(configPath)!);
            File.WriteAllText(configPath, "{}");

            var resolved = PathCompatibility.ResolveConfigPath("sample.json", tempRoot);

            Assert.Equal(configPath, resolved);
            Assert.Equal("/home/tester/.Rvbust/Data", PathCompatibility.BuildUserDataRoot("/home/tester", CompatibilityPathStyle.Posix));
            Assert.Equal(@"C:\Users\tester\.Rvbust\Data", PathCompatibility.BuildUserDataRoot(@"C:\Users\tester", CompatibilityPathStyle.Windows));
        }
        finally
        {
            Directory.Delete(tempRoot, recursive: true);
        }
    }

    /// <summary>
    /// 验证旧父工作区候选路径存在时也不会被相对配置解析隐式命中。
    /// </summary>
    [Fact]
    public void PathCompatibility_DoesNotResolveLegacyWorkspaceFallbacks()
    {
        var tempRoot = CreateTempDirectory();
        try
        {
            var legacyConfigPath = Path.Combine(tempRoot, "Rvbust", "Install", "FlyingShot", "Config", "sample.json");
            Directory.CreateDirectory(Path.GetDirectoryName(legacyConfigPath)!);
            File.WriteAllText(legacyConfigPath, "{}");

            var exception = Assert.Throws<FileNotFoundException>(() => PathCompatibility.ResolveConfigPath("sample.json", tempRoot));

            Assert.Equal("sample.json", exception.FileName);
        }
        finally
        {
            Directory.Delete(tempRoot, recursive: true);
        }
    }

    /// <summary>
    /// 验证默认加载配置时使用当前 replacement 仓库内的 Config/RobotConfig.json。
    /// </summary>
    [Fact]
    public void RobotConfigLoader_LoadsRepositoryConfigFromReplacementConfigDirectory()
    {
        var replacementRoot = GetReplacementRoot();

        var loaded = new RobotConfigLoader().Load("RobotConfig.json");

        Assert.Equal(Path.Combine(replacementRoot, "Config", "RobotConfig.json"), loaded.SourcePath);
    }

    /// <summary>
    /// 定位当前工作区根目录，便于复用父仓库中的真实样本。
    /// </summary>
    private static string GetWorkspaceRoot()
    {
        var current = new DirectoryInfo(AppContext.BaseDirectory);
        while (current is not null)
        {
            var slnPath = Path.Combine(current.FullName, "FlyshotReplacement.sln");
            if (File.Exists(slnPath))
            {
                return Path.GetFullPath(Path.Combine(current.FullName, ".."));
            }

            current = current.Parent;
        }

        throw new DirectoryNotFoundException("Unable to locate the flyshot workspace root.");
    }

    /// <summary>
    /// 定位 replacement 仓库根目录，供测试读取仓库内固化配置。
    /// </summary>
    private static string GetReplacementRoot()
    {
        var current = new DirectoryInfo(AppContext.BaseDirectory);
        while (current is not null)
        {
            if (File.Exists(Path.Combine(current.FullName, "FlyshotReplacement.sln")))
            {
                return current.FullName;
            }

            current = current.Parent;
        }

        throw new DirectoryNotFoundException("Unable to locate the flyshot replacement root.");
    }

    /// <summary>
    /// 创建当前测试专用的临时目录，避免不同测试之间相互污染。
    /// </summary>
    private static string CreateTempDirectory()
    {
        var tempPath = Path.Combine(Path.GetTempPath(), "flyshot-config-tests", Guid.NewGuid().ToString("N"));
        Directory.CreateDirectory(tempPath);
        return tempPath;
    }
}
