using QuanTAlib.Tests;

namespace QuanTAlib;

public class RsxValidationTests
{
    private readonly GBM _gbm;

    public RsxValidationTests()
    {
        _gbm = new GBM();
    }

    [Fact]
    public void Validate_Against_Reference_Implementation()
    {
        // Generate data
        const int count = 1000;
        int period = 14;
        var bars = _gbm.Fetch(count, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
        var prices = bars.Close.Values;

        // QuanTAlib implementation
        var rsx = new Rsx(period);
        var quantalibResults = new double[count];
        for (int i = 0; i < count; i++)
        {
            quantalibResults[i] = rsx.Update(new TValue(DateTime.UtcNow, prices[i])).Value;
        }

        // Reference implementation (from user prompt)
        var refRsx = new ReferenceRsx(period);
        var refResults = new double[count];
        for (int i = 0; i < count; i++)
        {
            refResults[i] = refRsx.Add(prices[i]);
        }

        // Compare
        for (int i = 0; i < count; i++)
        {
            // Allow small difference due to floating point arithmetic order
            Assert.Equal(refResults[i], quantalibResults[i], ValidationHelper.DefaultTolerance);
        }
    }

    // Reference implementation provided in the task description
    private class ReferenceRsx
    {
        private readonly double alpha, ialpha;
        // Internal state variables for filter registers:
        private double f28, f30, f38, f40, f48, f50;
        private double f58, f60, f68, f70, f78, f80;

        // Added state for f10 logic
        private double lastF8;
        private bool initialized;

        public double Current { get; private set; }

        public ReferenceRsx(int length)
        {
            // Initialize constants:
            this.alpha = 3.0 / (length + 2.0);
            this.ialpha = 1.0 - this.alpha;
            // Initialize filters to 0:
            f28 = f30 = f38 = f40 = f48 = f50 = 0.0;
            f58 = f60 = f68 = f70 = f78 = f80 = 0.0;
            this.Current = 50.0;  // neutral start
            this.initialized = false;
        }

        public double Add(double price)
        {
            // Core RSX calculations (assuming price input as closing price):
            double f8 = 100 * price;


            if (!initialized)
            {
                lastF8 = f8;
                initialized = true;
            }

            double v8 = f8 - lastF8;
            lastF8 = f8;

            // First smoothing stage:
            f28 = ialpha * f28 + alpha * v8;
            f30 = alpha * f28 + ialpha * f30;
            double vC = 1.5 * f28 - 0.5 * f30;
            // Second smoothing stage:
            f38 = ialpha * f38 + alpha * vC;
            f40 = alpha * f38 + ialpha * f40;
            double v10 = 1.5 * f38 - 0.5 * f40;
            // Third smoothing stage:
            f48 = ialpha * f48 + alpha * v10;
            f50 = alpha * f48 + ialpha * f50;
            double v14 = 1.5 * f48 - 0.5 * f50;
            // Repeat stages for absolute value (momentum magnitude):
            f58 = ialpha * f58 + alpha * Math.Abs(v8);
            f60 = alpha * f58 + ialpha * f60;
            double v18 = 1.5 * f58 - 0.5 * f60;
            f68 = ialpha * f68 + alpha * v18;
            f70 = alpha * f68 + ialpha * f70;
            double v1C = 1.5 * f68 - 0.5 * f70;
            f78 = ialpha * f78 + alpha * v1C;
            f80 = alpha * f78 + ialpha * f80;
            double v20 = 1.5 * f78 - 0.5 * f80;
            // Final RSX value:
            double rsx;
            if (v20 > 1e-10) // Avoid division by zero
            {
                double v4 = (v14 / v20 + 1.0) * 50.0;
                rsx = Math.Clamp(v4, 0.0, 100.0);
            }
            else
            {
                rsx = 50.0;
            }
            this.Current = rsx;
            return rsx;
        }
    }
}
