using RioJoy.Core.Calibration;
using RioJoy.Core.Protocol;
using Xunit;

namespace RioJoy.Core.Tests.Calibration;

public class AxisCalibratorTests
{
    // AnalogReport(throttle, leftPedal, rightPedal, joystickY, joystickX)
    private static AnalogReport Report(
        short throttle = 0, short left = 0, short right = 0, short y = 0, short x = 0) =>
        new(throttle, left, right, y, x);

    // --- Throttle (Z) --------------------------------------------------------

    [Fact]
    public void Throttle_BelowStart_IsProportional()
    {
        var cal = new AxisCalibrator();
        cal.ResetThrottle();

        // At-rest after reset: throttle equals its tracked start ⇒ centered at 0.
        Assert.Equal(0, cal.Update(Report(throttle: 0)).Z);

        // Pulled back 400 of 800 → half range × 32 = 16000.
        Assert.Equal(16000, cal.Update(Report(throttle: -400)).Z);

        // Full back (800) → 32000.
        Assert.Equal(32000, cal.Update(Report(throttle: -800)).Z);
    }

    [Fact]
    public void Throttle_OutOfRange_Saturates()
    {
        Assert.Equal(0, new AxisCalibrator().Update(Report(throttle: 900)).Z);       // > +range
        Assert.Equal(32000, new AxisCalibrator().Update(Report(throttle: -900)).Z);  // < -range
    }

    [Fact]
    public void Throttle_Invert_Flips()
    {
        var cal = new AxisCalibrator(new AxisCalibrationConfig { InvertZ = true });
        // throttle=900 → raw 0 → inverted to Max.
        Assert.Equal(AxisOutputs.Max, cal.Update(Report(throttle: 900)).Z);
    }

    // --- Pedals (Rx / Ry / Rz) ----------------------------------------------

    [Fact]
    public void Pedals_EnableZR_CentersRxRy_AndMixesRudder()
    {
        var cal = new AxisCalibrator(); // EnableZR default true
        AxisOutputs o = cal.Update(Report(left: 0, right: 0));

        Assert.Equal(AxisOutputs.Center, o.Rx);
        Assert.Equal(AxisOutputs.Center, o.Ry);
        Assert.Equal(AxisOutputs.Center, o.Rz); // rudder with both pedals at rest
    }

    [Fact]
    public void Pedals_DisableZR_DriveRxDirectly_AndCenterRz()
    {
        var cal = new AxisCalibrator(new AxisCalibrationConfig { EnableZR = false });

        // First sample sets the auto-range start; second (less depressed) produces a value.
        cal.Update(Report(left: -100));
        AxisOutputs o = cal.Update(Report(left: 0));

        // lP = (-100) - 0 = -100 → ×1000/500 = -200 → |·|×32 = 6400.
        Assert.Equal(6400, o.Rx);
        Assert.Equal(AxisOutputs.Center, o.Rz); // Rz centered when ZR disabled

        // The pedal readouts mirror Rx/Ry when there is no mix.
        Assert.Equal(o.Rx, cal.LeftPedalOutput);
        Assert.Equal(o.Ry, cal.RightPedalOutput);
    }

    [Fact]
    public void PedalOutputs_TrackPedals_EvenWithZrMix()
    {
        var cal = new AxisCalibrator(); // EnableZR default true

        cal.Update(Report(left: -100, right: -100));
        AxisOutputs o = cal.Update(Report(left: 0, right: 0));

        // The mix centers Rx/Ry, but the pedal readouts still track the pedals
        // (same math as the ZR-off Rx/Ry: 6400 for this deflection).
        Assert.Equal(AxisOutputs.Center, o.Rx);
        Assert.Equal(AxisOutputs.Center, o.Ry);
        Assert.Equal(6400, cal.LeftPedalOutput);
        Assert.Equal(6400, cal.RightPedalOutput);
    }

    // --- Joystick (X / Y) ----------------------------------------------------

    [Fact]
    public void Joystick_AtZero_IsCentered()
    {
        AxisOutputs o = new AxisCalibrator().Update(Report(x: 0, y: 0));
        Assert.Equal(AxisOutputs.Center, o.X);
        Assert.Equal(AxisOutputs.Center, o.Y);
    }

    [Fact]
    public void JoystickX_RightAndLeft_MoveOppositeSidesOfCenter()
    {
        // Right (positive raw) → below center; with auto-range seeded by this sample.
        Assert.Equal(52, new AxisCalibrator().Update(Report(x: 80)).X);
        // Left (negative raw) → above center.
        Assert.Equal(32733, new AxisCalibrator().Update(Report(x: -80)).X);
    }

    [Fact]
    public void JoystickX_Invert_Flips()
    {
        var cal = new AxisCalibrator(new AxisCalibrationConfig { InvertX = true });
        Assert.Equal(AxisOutputs.Max - 52, cal.Update(Report(x: 80)).X);
    }

    [Fact]
    public void ResetHorizontalJoystick_RecentersX()
    {
        var cal = new AxisCalibrator();
        Assert.Equal(52, cal.Update(Report(x: 80)).X);

        cal.ResetHorizontalJoystick();
        // After reset, X holds center until the stick next moves off zero.
        Assert.Equal(AxisOutputs.Center, cal.Update(Report(x: 0)).X);
    }

    [Fact]
    public void Outputs_AreClampedToAxisRange()
    {
        // The init-state throttle quirk yields a value far over range on the first
        // poll at rest; the documented 0..Max clamp guards it.
        Assert.Equal(AxisOutputs.Max, new AxisCalibrator().Update(Report(throttle: 0)).Z);
    }
}
