using System.Text.Json;
using Flyshot.Core.Domain;
using Microsoft.Extensions.Logging;

namespace Flyshot.Core.Config;

/// <summary>
/// 表示从旧版 RobotConfig.json 规范化后的机器人运行参数。
/// </summary>
public sealed class CompatibilityRobotSettings
{
    /// <summary>
    /// 初始化一份经过规范化的机器人兼容设置。
    /// </summary>
    public CompatibilityRobotSettings(
        bool useDo,
        IEnumerable<int> ioAddresses,
        int ioKeepCycles,
        int triggerSampleIndexOffsetCycles,
        double accLimitScale,
        double jerkLimitScale,
        int adaptIcspTryNum,
        double planningSpeedScale = 1.0,
        double speedRatio = 1.0,
        bool smoothStartStopTiming = true)
    {
        ArgumentNullException.ThrowIfNull(ioAddresses);

        if (ioKeepCycles < 0)
        {
            throw new ArgumentOutOfRangeException(nameof(ioKeepCycles), "IO 保持周期不能为负数。");
        }

        if (triggerSampleIndexOffsetCycles < 0)
        {
            throw new ArgumentOutOfRangeException(nameof(triggerSampleIndexOffsetCycles), "触发 sample 偏移周期不能为负数。");
        }

        if (accLimitScale <= 0.0)
        {
            throw new ArgumentOutOfRangeException(nameof(accLimitScale), "加速度倍率必须大于 0。");
        }

        if (jerkLimitScale <= 0.0)
        {
            throw new ArgumentOutOfRangeException(nameof(jerkLimitScale), "Jerk 倍率必须大于 0。");
        }

        if (planningSpeedScale <= 0.0 || double.IsNaN(planningSpeedScale) || double.IsInfinity(planningSpeedScale))
        {
            throw new ArgumentOutOfRangeException(nameof(planningSpeedScale), "规划速度倍率必须是有限正数。");
        }

        if (speedRatio <= 0.0 || speedRatio > 1.0 || double.IsNaN(speedRatio) || double.IsInfinity(speedRatio))
        {
            throw new ArgumentOutOfRangeException(nameof(speedRatio), "运行时速度倍率必须在 (0, 1] 范围内。");
        }

        if (adaptIcspTryNum < 0)
        {
            throw new ArgumentOutOfRangeException(nameof(adaptIcspTryNum), "补点尝试次数不能为负数。");
        }

        // 固化地址数组，避免上层代码在加载后原地篡改配置内容。
        var copiedIoAddresses = ioAddresses.ToArray();
        if (copiedIoAddresses.Any(address => address < 0))
        {
            throw new ArgumentException("IO 地址不能为负数。", nameof(ioAddresses));
        }

        UseDo = useDo;
        IoAddresses = copiedIoAddresses;
        IoKeepCycles = ioKeepCycles;
        TriggerSampleIndexOffsetCycles = triggerSampleIndexOffsetCycles;
        AccLimitScale = accLimitScale;
        JerkLimitScale = jerkLimitScale;
        AdaptIcspTryNum = adaptIcspTryNum;
        PlanningSpeedScale = planningSpeedScale;
        SpeedRatio = speedRatio;
        SmoothStartStopTiming = smoothStartStopTiming;
    }

    /// <summary>
    /// 获取是否启用伺服同步 IO。
    /// </summary>
    public bool UseDo { get; }

    /// <summary>
    /// 获取默认 IO 地址组。
    /// </summary>
    public IReadOnlyList<int> IoAddresses { get; }

    /// <summary>
    /// 获取单次触发保持的伺服周期数。
    /// </summary>
    public int IoKeepCycles { get; }

    /// <summary>
    /// 获取触发绑定到最佳 sample 后，还要再向后偏移的命令周期数。
    /// 该值作用在 J519 命令 sample 时间轴，不是 60015 状态反馈周期。
    /// </summary>
    public int TriggerSampleIndexOffsetCycles { get; }

    /// <summary>
    /// 获取加速度全局倍率。
    /// </summary>
    public double AccLimitScale { get; }

    /// <summary>
    /// 获取 Jerk 全局倍率。
    /// </summary>
    public double JerkLimitScale { get; }

    /// <summary>
    /// 获取规划阶段的全局速度倍率，只影响 JointTraj 基准时间，不等同于运行时 J519 下发速度倍率。
    /// </summary>
    public double PlanningSpeedScale { get; }

    /// <summary>
    /// 获取运行时默认速度倍率，会在 SetUpRobot 时同步到控制器运行时，供 MoveJoint 和后续执行链路使用。
    /// </summary>
    public double SpeedRatio { get; }

    /// <summary>
    /// 获取是否在飞拍执行前对整段时间轴做二次平滑起停重映射。
    /// </summary>
    public bool SmoothStartStopTiming { get; }

    /// <summary>
    /// 获取自适应补点最大尝试次数。
    /// </summary>
    public int AdaptIcspTryNum { get; }
}

/// <summary>
/// 表示一次旧版 RobotConfig.json 的完整加载结果。
/// </summary>
public sealed class LoadedRobotConfig
{
    /// <summary>
    /// 初始化一份规范化后的旧配置文档。
    /// </summary>
    public LoadedRobotConfig(
        string sourcePath,
        CompatibilityRobotSettings robot,
        IReadOnlyDictionary<string, FlyshotProgram> programs)
    {
        if (string.IsNullOrWhiteSpace(sourcePath))
        {
            throw new ArgumentException("配置源路径不能为空。", nameof(sourcePath));
        }

        SourcePath = sourcePath;
        Robot = robot ?? throw new ArgumentNullException(nameof(robot));
        Programs = programs ?? throw new ArgumentNullException(nameof(programs));
    }

    /// <summary>
    /// 获取实际命中的配置文件路径。
    /// </summary>
    public string SourcePath { get; }

    /// <summary>
    /// 获取机器人级兼容设置。
    /// </summary>
    public CompatibilityRobotSettings Robot { get; }

    /// <summary>
    /// 获取按名称索引的飞拍程序集合。
    /// </summary>
    public IReadOnlyDictionary<string, FlyshotProgram> Programs { get; }
}

/// <summary>
/// 负责读取旧版 RobotConfig.json 并转换成领域层可直接消费的结构。
/// </summary>
public sealed class RobotConfigLoader
{
    private readonly ILogger<RobotConfigLoader>? _logger;

    /// <summary>
    /// 初始化 RobotConfigLoader。
    /// </summary>
    /// <param name="logger">日志记录器；允许 null。</param>
    public RobotConfigLoader(ILogger<RobotConfigLoader>? logger = null)
    {
        _logger = logger;
    }

    /// <summary>
    /// 加载一份旧版 RobotConfig.json。
    /// </summary>
    /// <param name="configPath">调用方传入的配置路径。</param>
    /// <param name="repoRoot">用于兼容搜索的仓库根目录；为空时按当前工作目录推断。</param>
    /// <returns>规范化后的配置文档。</returns>
    public LoadedRobotConfig Load(string configPath, string? repoRoot = null)
    {
        _logger?.LogInformation("RobotConfig 开始加载: configPath={ConfigPath}, repoRoot={RepoRoot}", configPath, repoRoot);

        var resolvedRepoRoot = ResolveRepoRoot(repoRoot);
        var resolvedConfigPath = PathCompatibility.ResolveConfigPath(configPath, resolvedRepoRoot);

        using var document = JsonDocument.Parse(File.ReadAllText(resolvedConfigPath));
        var root = document.RootElement;
        var robotElement = root.GetProperty("robot");
        var flyingShotsElement = root.GetProperty("flying_shots");

        var robot = new CompatibilityRobotSettings(
            useDo: ReadBoolean(robotElement, "use_do", defaultValue: false),
            ioAddresses: ReadIntArray(robotElement, "io_addr"),
            ioKeepCycles: ReadInt(robotElement, "io_keep_cycles", defaultValue: 0),
            triggerSampleIndexOffsetCycles: ReadInt(robotElement, "trigger_sample_index_offset_cycles", defaultValue: 7),
            accLimitScale: ReadDouble(robotElement, "acc_limit", defaultValue: 1.0),
            jerkLimitScale: ReadDouble(robotElement, "jerk_limit", defaultValue: 1.0),
            adaptIcspTryNum: ReadInt(robotElement, "adapt_icsp_try_num", defaultValue: 0),
            planningSpeedScale: ReadDouble(robotElement, "planning_speed_scale", defaultValue: 1.0),
            speedRatio: ReadDouble(robotElement, "speed_ratio", defaultValue: 1.0),
            smoothStartStopTiming: ReadBoolean(robotElement, "smooth_start_stop_timing", defaultValue: true));

        var programs = new Dictionary<string, FlyshotProgram>(StringComparer.Ordinal);
        foreach (var programElement in flyingShotsElement.EnumerateObject())
        {
            var programName = programElement.Name;
            var program = ParseProgram(programName, programElement.Value);
            programs.Add(programName, program);
        }

        _logger?.LogInformation(
            "RobotConfig 加载完成: resolvedPath={ResolvedPath}, useDo={UseDo}, ioKeepCycles={IoKeepCycles}, accLimit={AccLimit}, jerkLimit={JerkLimit}, planningSpeedScale={PlanningSpeedScale}, speedRatio={SpeedRatio}, smoothStartStopTiming={SmoothStartStopTiming}, adaptIcspTryNum={AdaptIcspTryNum}, 程序数={ProgramCount}",
            resolvedConfigPath, robot.UseDo, robot.IoKeepCycles, robot.AccLimitScale, robot.JerkLimitScale, robot.PlanningSpeedScale, robot.SpeedRatio, robot.SmoothStartStopTiming, robot.AdaptIcspTryNum, programs.Count);

        return new LoadedRobotConfig(
            sourcePath: resolvedConfigPath,
            robot: robot,
            programs: programs);
    }

    /// <summary>
    /// 把单个 flying_shots 节点转成领域层 FlyshotProgram。
    /// </summary>
    private static FlyshotProgram ParseProgram(string programName, JsonElement programElement)
    {
        var waypointRows = programElement.GetProperty("traj_waypoints").EnumerateArray().ToArray();
        var shotFlagElements = programElement.GetProperty("shot_flags").EnumerateArray().ToArray();

        var waypoints = waypointRows
            .Select(row => new JointWaypoint(row.EnumerateArray().Select(static value => value.GetDouble())))
            .ToArray();

        var shotFlags = shotFlagElements
            .Select(static value => value.ValueKind == JsonValueKind.True || (value.ValueKind == JsonValueKind.Number && value.GetInt32() != 0))
            .ToArray();

        var waypointCount = waypoints.Length;
        var offsetValues = programElement.TryGetProperty("offset_values", out var offsetValuesElement)
            ? offsetValuesElement.EnumerateArray().Select(static value => value.GetInt32()).ToArray()
            : Enumerable.Repeat(0, waypointCount).ToArray();

        var addressGroups = programElement.TryGetProperty("addr", out var addressElement)
            ? addressElement
                .EnumerateArray()
                .Select(group => new IoAddressGroup(group.EnumerateArray().Select(static value => value.GetInt32())))
                .ToArray()
            : Enumerable.Range(0, waypointCount)
                .Select(_ => new IoAddressGroup(Array.Empty<int>()))
                .ToArray();

        return new FlyshotProgram(
            name: programName,
            waypoints: waypoints,
            shotFlags: shotFlags,
            offsetValues: offsetValues,
            addressGroups: addressGroups);
    }

    /// <summary>
    /// 解析布尔或 0/1 风格的兼容字段。
    /// </summary>
    private static bool ReadBoolean(JsonElement parent, string propertyName, bool defaultValue)
    {
        if (!parent.TryGetProperty(propertyName, out var property))
        {
            return defaultValue;
        }

        return property.ValueKind switch
        {
            JsonValueKind.True => true,
            JsonValueKind.False => false,
            JsonValueKind.Number => property.GetInt32() != 0,
            _ => defaultValue
        };
    }

    /// <summary>
    /// 读取整数数组；字段不存在时返回空数组。
    /// </summary>
    private static IReadOnlyList<int> ReadIntArray(JsonElement parent, string propertyName)
    {
        return parent.TryGetProperty(propertyName, out var property)
            ? property.EnumerateArray().Select(static value => value.GetInt32()).ToArray()
            : Array.Empty<int>();
    }

    /// <summary>
    /// 读取整数兼容字段。
    /// </summary>
    private static int ReadInt(JsonElement parent, string propertyName, int defaultValue)
    {
        return parent.TryGetProperty(propertyName, out var property) ? property.GetInt32() : defaultValue;
    }

    /// <summary>
    /// 读取浮点兼容字段。
    /// </summary>
    private static double ReadDouble(JsonElement parent, string propertyName, double defaultValue)
    {
        return parent.TryGetProperty(propertyName, out var property) ? property.GetDouble() : defaultValue;
    }

    /// <summary>
    /// 推断当前 replacement 仓库根目录，优先使用调用方显式传入的值。
    /// </summary>
    private static string ResolveRepoRoot(string? repoRoot)
    {
        if (!string.IsNullOrWhiteSpace(repoRoot))
        {
            return Path.GetFullPath(repoRoot);
        }

        var current = new DirectoryInfo(Directory.GetCurrentDirectory());
        while (current is not null)
        {
            if (File.Exists(Path.Combine(current.FullName, "FlyshotReplacement.sln")))
            {
                return current.FullName;
            }

            current = current.Parent;
        }

        return Directory.GetCurrentDirectory();
    }
}
