using System.Net;
using System.Net.Http.Json;
using System.Text;
using System.Text.Json;

namespace Flyshot.Server.IntegrationTests;

/// <summary>
/// 锁定 flyshot-uaes-interface 现有 FastAPI 层的 HTTP 路径、参数绑定和返回 JSON 外形。
/// </summary>
public sealed class LegacyHttpApiCompatibilityTests(FlyshotServerFactory factory) : IClassFixture<FlyshotServerFactory>
{
    /// <summary>
    /// 验证根路径会返回旧 HTTP 服务使用的 Hello World JSON，而不是跳转到健康检查页。
    /// </summary>
    [Fact]
    public async Task Root_ReturnsLegacyHelloWorldPayload()
    {
        using var client = factory.CreateClient();

        using var response = await client.GetAsync("/");
        Assert.Equal(HttpStatusCode.OK, response.StatusCode);

        using var json = await ReadJsonAsync(response);
        Assert.Equal("Hello World", json.RootElement.GetProperty("message").GetString());
    }

    /// <summary>
    /// 验证初始化链路和机器人信息接口会保持旧 FastAPI 服务的路径与返回字段风格。
    /// </summary>
    [Fact]
    public async Task InitEndpoints_ExposeLegacyRobotInfoAndSpeedRatioShape()
    {
        using var client = factory.CreateClient();

        using (var connectServerResponse = await client.PostAsync("/connect_server/?server_ip=127.0.0.1&port=50001", content: null))
        {
            Assert.Equal(HttpStatusCode.OK, connectServerResponse.StatusCode);
            using var connectServerJson = await ReadJsonAsync(connectServerResponse);
            Assert.Equal("connected", connectServerJson.RootElement.GetProperty("status").GetString());
        }

        using (var setupResponse = await client.PostAsync("/setup_robot/?robot_name=FANUC_LR_Mate_200iD", content: null))
        {
            Assert.Equal(HttpStatusCode.OK, setupResponse.StatusCode);
            using var setupJson = await ReadJsonAsync(setupResponse);
            Assert.Equal("robot setup", setupJson.RootElement.GetProperty("status").GetString());
        }

        using (var isSetupResponse = await client.GetAsync("/is_setup/"))
        {
            Assert.Equal(HttpStatusCode.OK, isSetupResponse.StatusCode);
            using var isSetupJson = await ReadJsonAsync(isSetupResponse);
            Assert.True(isSetupJson.RootElement.GetProperty("is_setup").GetBoolean());
        }

        using (var activeControllerResponse = await client.PostAsync("/set_active_controller/?sim=true", content: null))
        {
            Assert.Equal(HttpStatusCode.OK, activeControllerResponse.StatusCode);
            using var activeControllerJson = await ReadJsonAsync(activeControllerResponse);
            Assert.Equal("active controller set", activeControllerJson.RootElement.GetProperty("status").GetString());
        }

        using (var connectRobotResponse = await client.PostAsync("/connect_robot/?ip=192.168.10.101", content: null))
        {
            Assert.Equal(HttpStatusCode.OK, connectRobotResponse.StatusCode);
            using var connectRobotJson = await ReadJsonAsync(connectRobotResponse);
            Assert.Equal("robot connected", connectRobotJson.RootElement.GetProperty("status").GetString());
        }

        using (var enableRobotResponse = await client.GetAsync("/enable_robot/"))
        {
            Assert.Equal(HttpStatusCode.OK, enableRobotResponse.StatusCode);
            using var enableRobotJson = await ReadJsonAsync(enableRobotResponse);
            Assert.True(enableRobotJson.RootElement.GetProperty("enable_robot").GetBoolean());
        }

        using (var robotInfoResponse = await client.GetAsync("/robot_info/"))
        {
            Assert.Equal(HttpStatusCode.OK, robotInfoResponse.StatusCode);
            using var robotInfoJson = await ReadJsonAsync(robotInfoResponse);
            var robotInfoRoot = robotInfoJson.RootElement;

            Assert.Equal("FANUC_LR_Mate_200iD", robotInfoRoot.GetProperty("name").GetString());
            Assert.Equal("flyshot-replacement-controller-client-compat/0.1.0", robotInfoRoot.GetProperty("server_version").GetString());
            Assert.Equal(6, robotInfoRoot.GetProperty("dof").GetInt32());
            Assert.Equal(1.0, robotInfoRoot.GetProperty("speed_ratio").GetDouble(), precision: 6);
        }

        using (var setSpeedRatioResponse = await client.PostAsJsonAsync("/set_speedRatio/", new { speed = 0.8 }))
        {
            Assert.Equal(HttpStatusCode.OK, setSpeedRatioResponse.StatusCode);
            using var setSpeedRatioJson = await ReadJsonAsync(setSpeedRatioResponse);
            Assert.Equal("set_speedRatio executed", setSpeedRatioJson.RootElement.GetProperty("message").GetString());
            Assert.Equal(0, setSpeedRatioJson.RootElement.GetProperty("returnCode").GetInt32());
        }

        using var updatedRobotInfoResponse = await client.GetAsync("/robot_info/");
        Assert.Equal(HttpStatusCode.OK, updatedRobotInfoResponse.StatusCode);
        using var updatedRobotInfoJson = await ReadJsonAsync(updatedRobotInfoResponse);
        Assert.Equal(0.8, updatedRobotInfoJson.RootElement.GetProperty("speed_ratio").GetDouble(), precision: 6);
    }

    /// <summary>
    /// 验证 TCP、关节位置和位姿相关 HTTP 接口会保持旧服务的请求体与响应体结构。
    /// </summary>
    [Fact]
    public async Task MotionStateEndpoints_RoundTripLegacyPayloadShapes()
    {
        using var client = factory.CreateClient();
        await InitializeRobotAsync(client);

        using (var setTcpResponse = await client.PostAsJsonAsync("/set_tcp/", new { x = 1.0, y = 2.0, z = 3.0 }))
        {
            Assert.Equal(HttpStatusCode.OK, setTcpResponse.StatusCode);
            using var setTcpJson = await ReadJsonAsync(setTcpResponse);
            Assert.Equal("TCP set", setTcpJson.RootElement.GetProperty("status").GetString());
        }

        using (var getTcpResponse = await client.GetAsync("/get_tcp/"))
        {
            Assert.Equal(HttpStatusCode.OK, getTcpResponse.StatusCode);
            using var getTcpJson = await ReadJsonAsync(getTcpResponse);
            var tcpValues = getTcpJson.RootElement.GetProperty("tcp").EnumerateArray().Select(static value => value.GetDouble()).ToArray();
            Assert.Equal([1.0, 2.0, 3.0], tcpValues);
        }

        using (var moveJointResponse = await client.PostAsJsonAsync("/move_joint/", new { joints = new[] { 1.0, 2.0, 3.0, 4.0, 5.0, 6.0 } }))
        {
            Assert.Equal(HttpStatusCode.OK, moveJointResponse.StatusCode);
            using var moveJointJson = await ReadJsonAsync(moveJointResponse);
            Assert.Equal("robot moved", moveJointJson.RootElement.GetProperty("status").GetString());
        }

        using (var movePoseResponse = await client.PostAsJsonAsync("/move_pose/", new { x = 100.0, y = 200.0, z = 300.0, w = 1.0, p = 2.0, r = 3.0 }))
        {
            Assert.Equal(HttpStatusCode.OK, movePoseResponse.StatusCode);
            using var movePoseJson = await ReadJsonAsync(movePoseResponse);
            Assert.Equal("robot moved", movePoseJson.RootElement.GetProperty("status").GetString());
        }

        using (var getJointPositionResponse = await client.GetAsync("/get_joint_position/"))
        {
            Assert.Equal(HttpStatusCode.OK, getJointPositionResponse.StatusCode);
            using var getJointPositionJson = await ReadJsonAsync(getJointPositionResponse);
            var root = getJointPositionJson.RootElement;
            Assert.True(root.GetProperty("success").GetBoolean());
            var jointValues = root.GetProperty("points").EnumerateArray().Select(static value => value.GetDouble()).ToArray();
            Assert.Equal([1.0, 2.0, 3.0, 4.0, 5.0, 6.0], jointValues);
        }

        using var getPoseResponse = await client.GetAsync("/get_pose");
        Assert.Equal(HttpStatusCode.OK, getPoseResponse.StatusCode);
        using var getPoseJson = await ReadJsonAsync(getPoseResponse);
        Assert.Equal(7, getPoseJson.RootElement.GetProperty("pose").GetArrayLength());

        using (var executeTrajectoryResponse = await client.PostAsJsonAsync("/execute_trajectory/", new
        {
            method = "icsp",
            save_traj = true,
            waypoints = new[]
            {
                new[] { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 },
                new[] { 0.1, 0.0, 0.0, 0.0, 0.0, 0.0 },
                new[] { 0.2, 0.0, 0.0, 0.0, 0.0, 0.0 },
                new[] { 0.3, 0.0, 0.0, 0.0, 0.0, 0.0 }
            }
        }))
        {
            Assert.Equal(HttpStatusCode.OK, executeTrajectoryResponse.StatusCode);
            using var executeTrajectoryJson = await ReadJsonAsync(executeTrajectoryResponse);
            Assert.Equal("trajectory executed", executeTrajectoryJson.RootElement.GetProperty("status").GetString());
        }
    }

    /// <summary>
    /// 验证 MovePose 请求必须显式提供六个有限直角坐标字段，避免缺字段被模型绑定静默补 0。
    /// </summary>
    [Theory]
    [InlineData("""{"x":100.0,"y":200.0,"z":300.0,"w":1.0,"p":2.0}""")]
    [InlineData("null")]
    [InlineData("""{"x":1e999,"y":200.0,"z":300.0,"w":1.0,"p":2.0,"r":3.0}""")]
    [InlineData("""{"x":1000.1,"y":200.0,"z":300.0,"w":1.0,"p":2.0,"r":3.0}""")]
    [InlineData("""{"x":100.0,"y":-1000.1,"z":300.0,"w":1.0,"p":2.0,"r":3.0}""")]
    [InlineData("""{"x":100.0,"y":200.0,"z":-0.1,"w":1.0,"p":2.0,"r":3.0}""")]
    [InlineData("""{"x":100.0,"y":200.0,"z":300.0,"w":-180.1,"p":2.0,"r":3.0}""")]
    [InlineData("""{"x":100.0,"y":200.0,"z":300.0,"w":1.0,"p":90.1,"r":3.0}""")]
    [InlineData("""{"x":100.0,"y":200.0,"z":300.0,"w":1.0,"p":2.0,"r":180.1}""")]
    public async Task MovePose_InvalidPayload_ReturnsLegacyBadRequest(string payload)
    {
        using var client = factory.CreateClient();
        await InitializeRobotAsync(client);

        using var content = new StringContent(payload, Encoding.UTF8, "application/json");
        using var response = await client.PostAsync("/move_pose/", content);

        Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
        using var json = await ReadJsonAsync(response);
        Assert.Equal("MovePose failed", json.RootElement.GetProperty("detail").GetString());
    }

    /// <summary>
    /// 验证飞拍 HTTP 接口可以按旧 API 层的路径和字段完成上传、列出、执行与删除。
    /// </summary>
    [Fact]
    public async Task FlyshotEndpoints_RoundTripLegacyUploadExecuteAndDeleteFlow()
    {
        using var client = factory.CreateClient();
        await InitializeRobotAsync(client);

        var uploadPayload = new
        {
            addrs = new[]
            {
                new[] { 7, 8 },
                new[] { 7, 8 },
                Array.Empty<int>(),
                Array.Empty<int>()
            },
            name = "demo-http-flyshot",
            offset_values = new[] { 0.0, 1.0, 0.0, 0.0 },
            shot_flags = new[] { false, true, false, false },
            waypoints = new[]
            {
                new[] { 0.1, 0.2, 0.3, 0.4, 0.5, 0.6 },
                new[] { 0.2, 0.2, 0.3, 0.4, 0.5, 0.6 },
                new[] { 0.3, 0.2, 0.3, 0.4, 0.5, 0.6 },
                new[] { 0.4, 0.2, 0.3, 0.4, 0.5, 0.6 }
            }
        };

        using (var uploadResponse = await client.PostAsJsonAsync("/upload_flyshot/", uploadPayload))
        {
            Assert.Equal(HttpStatusCode.OK, uploadResponse.StatusCode);
            using var uploadJson = await ReadJsonAsync(uploadResponse);
            Assert.Equal("FlyShot uploaded", uploadJson.RootElement.GetProperty("status").GetString());
        }

        using (var listResponse = await client.GetAsync("/list_flyShotTraj/"))
        {
            Assert.Equal(HttpStatusCode.OK, listResponse.StatusCode);
            using var listJson = await ReadJsonAsync(listResponse);
            var names = listJson.RootElement.GetProperty("flyshot_trajs").EnumerateArray().Select(static value => value.GetString()).ToArray();
            Assert.Contains("demo-http-flyshot", names);
        }

        using (var validResponse = await client.PostAsJsonAsync("/is_flyShotTrajValid/", new
        {
            name = "demo-http-flyshot",
            method = "icsp",
            save_traj = false
        }))
        {
            Assert.Equal(HttpStatusCode.OK, validResponse.StatusCode);
            using var validJson = await ReadJsonAsync(validResponse);
            Assert.True(validJson.RootElement.GetProperty("valid").GetBoolean());
            Assert.True(validJson.RootElement.GetProperty("time").GetDouble() > 0.0);
        }

        using (var executeResponse = await client.PostAsJsonAsync("/execute_flyshot/", new
        {
            name = "demo-http-flyshot",
            move_to_start = true,
            method = "icsp",
            save_traj = true,
            use_cache = true,
            wait = true
        }))
        {
            Assert.Equal(HttpStatusCode.OK, executeResponse.StatusCode);
            using var executeJson = await ReadJsonAsync(executeResponse);
            var executeRoot = executeJson.RootElement;
            Assert.Equal("FlyShot executed", executeRoot.GetProperty("status").GetString());
            Assert.True(executeRoot.GetProperty("success").GetBoolean());
        }

        using (var saveInfoResponse = await client.PostAsJsonAsync("/save_traj_info/", new
        {
            name = "demo-http-flyshot",
            method = "icsp"
        }))
        {
            Assert.Equal(HttpStatusCode.OK, saveInfoResponse.StatusCode);
            using var saveInfoJson = await ReadJsonAsync(saveInfoResponse);
            Assert.True(saveInfoJson.RootElement.GetProperty("success").GetBoolean());
        }

        using (var deleteResponse = await client.PostAsJsonAsync("/delete_flyshot/", new { name = "demo-http-flyshot" }))
        {
            Assert.Equal(HttpStatusCode.OK, deleteResponse.StatusCode);
            using var deleteJson = await ReadJsonAsync(deleteResponse);
            Assert.Equal("FlyShot deleted", deleteJson.RootElement.GetProperty("status").GetString());
        }
    }

    /// <summary>
    /// 复用旧 API 层常见的初始化顺序，把当前宿主推进到可执行动作的最小状态。
    /// </summary>
    private static async Task InitializeRobotAsync(HttpClient client)
    {
        using var initResponse = await client.PostAsJsonAsync("/init_mpc_robt", new
        {
            server_ip = "127.0.0.1",
            port = 50001,
            robot_name = "FANUC_LR_Mate_200iD",
            robot_ip = "192.168.10.101",
            sim = true
        });

        Assert.Equal(HttpStatusCode.OK, initResponse.StatusCode);
        using var initJson = await ReadJsonAsync(initResponse);
        Assert.Equal("init_Success", initJson.RootElement.GetProperty("message").GetString());
        Assert.Equal(0, initJson.RootElement.GetProperty("returnCode").GetInt32());
    }

    /// <summary>
    /// 统一把 HTTP 响应体解析成 JsonDocument，便于对旧接口的字段形状做精确断言。
    /// </summary>
    private static async Task<JsonDocument> ReadJsonAsync(HttpResponseMessage response)
    {
        await using var responseStream = await response.Content.ReadAsStreamAsync();
        return await JsonDocument.ParseAsync(responseStream);
    }
}
