using TradingPlatform.BusinessLayer;

namespace QuanTAlib.Tests;

public class VamaIndicatorTests
{
    [Fact]
    public void VamaIndicator_Constructor_SetsDefaults()
    {
        var indicator = new VamaIndicator();

        Assert.Equal(20, indicator.BaseLength);
        Assert.Equal(10, indicator.ShortAtrPeriod);
        Assert.Equal(50, indicator.LongAtrPeriod);
        Assert.Equal(5, indicator.MinLength);
        Assert.Equal(100, indicator.MaxLength);
        Assert.True(indicator.ShowColdValues);
        Assert.Equal("VAMA - Volatility Adjusted Moving Average", indicator.Name);
        Assert.False(indicator.SeparateWindow);
        Assert.True(indicator.OnBackGround);
    }

    [Fact]
    public void VamaIndicator_MinHistoryDepths_EqualsZero()
    {
        var indicator = new VamaIndicator { BaseLength = 20 };

        Assert.Equal(0, VamaIndicator.MinHistoryDepths);
        Assert.Equal(0, ((IWatchlistIndicator)indicator).MinHistoryDepths);
    }

    [Fact]
    public void VamaIndicator_ShortName_IncludesParameters()
    {
        var indicator = new VamaIndicator
        {
            BaseLength = 15,
            ShortAtrPeriod = 8,
            LongAtrPeriod = 40
        };

        Assert.Contains("VAMA", indicator.ShortName, StringComparison.Ordinal);
        Assert.Contains("15", indicator.ShortName, StringComparison.Ordinal);
        Assert.Contains("8", indicator.ShortName, StringComparison.Ordinal);
        Assert.Contains("40", indicator.ShortName, StringComparison.Ordinal);
    }

    [Fact]
    public void VamaIndicator_Initialize_CreatesInternalVama()
    {
        var indicator = new VamaIndicator { BaseLength = 10 };

        // Initialize should not throw
        indicator.Initialize();

        // After init, line series should exist
        Assert.Single(indicator.LinesSeries);
    }

    [Fact]
    public void VamaIndicator_ProcessUpdate_HistoricalBar_ComputesValue()
    {
        var indicator = new VamaIndicator { BaseLength = 5 };
        indicator.Initialize();

        // Add historical data with OHLC
        var now = DateTime.UtcNow;
        indicator.HistoricalData.AddBar(now, 100, 105, 95, 102);

        // Process update
        var args = new UpdateArgs(UpdateReason.HistoricalBar);
        indicator.ProcessUpdate(args);

        // Line series should have a value
        Assert.Equal(1, indicator.LinesSeries[0].Count);
        Assert.True(double.IsFinite(indicator.LinesSeries[0].GetValue(0)));
    }

    [Fact]
    public void VamaIndicator_ProcessUpdate_NewBar_ComputesValue()
    {
        var indicator = new VamaIndicator { BaseLength = 5 };
        indicator.Initialize();

        // Add historical data with varying volatility
        var now = DateTime.UtcNow;
        indicator.HistoricalData.AddBar(now, 100, 105, 95, 102);
        indicator.HistoricalData.AddBar(now.AddMinutes(1), 102, 110, 98, 106);

        // Process first update
        indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
        indicator.ProcessUpdate(new UpdateArgs(UpdateReason.NewBar));

        // Line series should have values
        Assert.Equal(2, indicator.LinesSeries[0].Count);
    }

    [Fact]
    public void VamaIndicator_ProcessUpdate_NewTick_ProcessesWithoutError()
    {
        var indicator = new VamaIndicator { BaseLength = 5 };
        indicator.Initialize();

        // Add historical data
        var now = DateTime.UtcNow;
        indicator.HistoricalData.AddBar(now, 100, 105, 95, 102);

        // Process historical bar first
        indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
        double firstValue = indicator.LinesSeries[0].GetValue(0);

        // Update with new tick (same bar data - simulates intrabar update)
        indicator.ProcessUpdate(new UpdateArgs(UpdateReason.NewTick));
        double secondValue = indicator.LinesSeries[0].GetValue(0);

        // Both values should be finite
        Assert.True(double.IsFinite(firstValue));
        Assert.True(double.IsFinite(secondValue));
    }

    [Fact]
    public void VamaIndicator_MultipleUpdates_ProducesCorrectSequence()
    {
        var indicator = new VamaIndicator { BaseLength = 5, ShortAtrPeriod = 3, LongAtrPeriod = 10 };
        indicator.Initialize();

        var now = DateTime.UtcNow;
        // Create bars with varying volatility
        (double o, double h, double l, double c)[] bars =
        {
            (100, 102, 98, 101),   // Low volatility
            (101, 103, 99, 102),
            (102, 104, 100, 103),
            (103, 108, 97, 105),   // Higher volatility
            (105, 112, 100, 110),
            (110, 115, 105, 108),
            (108, 110, 106, 109),  // Back to lower
            (109, 111, 107, 110),
            (110, 112, 108, 111),
            (111, 113, 109, 112)
        };

        foreach (var (o, h, l, c) in bars)
        {
            indicator.HistoricalData.AddBar(now, o, h, l, c);
            indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
            now = now.AddMinutes(1);
        }

        // All values should be finite
        for (int i = 0; i < bars.Length; i++)
        {
            Assert.True(double.IsFinite(indicator.LinesSeries[0].GetValue(bars.Length - 1 - i)));
        }

        // VAMA should be smoothing the values
        double lastVama = indicator.LinesSeries[0].GetValue(0);
        Assert.True(lastVama >= 95 && lastVama <= 120);
    }

    [Fact]
    public void VamaIndicator_HighVolatility_ShorterPeriod()
    {
        // Test that high volatility results in shorter effective period (faster response)
        var indicator = new VamaIndicator
        {
            BaseLength = 20,
            ShortAtrPeriod = 5,
            LongAtrPeriod = 20,
            MinLength = 5,
            MaxLength = 50
        };
        indicator.Initialize();

        var now = DateTime.UtcNow;

        // Start with low volatility period
        for (int i = 0; i < 30; i++)
        {
            double price = 100 + i * 0.1;
            indicator.HistoricalData.AddBar(now, price, price + 1, price - 1, price);
            indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
            now = now.AddMinutes(1);
        }

        double afterLowVol = indicator.LinesSeries[0].GetValue(0);

        // Now add high volatility bars
        for (int i = 0; i < 10; i++)
        {
            double price = 103 + i;
            indicator.HistoricalData.AddBar(now, price, price + 5, price - 5, price + 2);
            indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
            now = now.AddMinutes(1);
        }

        double afterHighVol = indicator.LinesSeries[0].GetValue(0);

        // Both should be finite
        Assert.True(double.IsFinite(afterLowVol));
        Assert.True(double.IsFinite(afterHighVol));
    }

    [Fact]
    public void VamaIndicator_Parameters_CanBeChanged()
    {
        var indicator = new VamaIndicator { BaseLength = 10 };
        Assert.Equal(10, indicator.BaseLength);

        indicator.BaseLength = 30;
        Assert.Equal(30, indicator.BaseLength);

        indicator.ShortAtrPeriod = 15;
        Assert.Equal(15, indicator.ShortAtrPeriod);

        indicator.LongAtrPeriod = 60;
        Assert.Equal(60, indicator.LongAtrPeriod);

        indicator.MinLength = 3;
        Assert.Equal(3, indicator.MinLength);

        indicator.MaxLength = 200;
        Assert.Equal(200, indicator.MaxLength);
    }

    [Fact]
    public void VamaIndicator_LongPeriod_Works()
    {
        var indicator = new VamaIndicator
        {
            BaseLength = 50,
            ShortAtrPeriod = 20,
            LongAtrPeriod = 100,
            MaxLength = 200
        };
        indicator.Initialize();

        var now = DateTime.UtcNow;
        for (int i = 0; i < 200; i++)
        {
            double price = 100 + (i * 0.1) + Math.Sin(i * 0.1) * 2;
            indicator.HistoricalData.AddBar(now.AddMinutes(i), price, price + 2, price - 2, price);
            indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
        }

        // Last value should be finite and in reasonable range
        double lastValue = indicator.LinesSeries[0].GetValue(0);
        Assert.True(double.IsFinite(lastValue));
        Assert.True(lastValue > 100 && lastValue < 130);
    }

    [Fact]
    public void VamaIndicator_ShortPeriod_Works()
    {
        var indicator = new VamaIndicator
        {
            BaseLength = 5,
            ShortAtrPeriod = 3,
            LongAtrPeriod = 10,
            MinLength = 2,
            MaxLength = 20
        };
        indicator.Initialize();

        var now = DateTime.UtcNow;
        (double o, double h, double l, double c)[] bars =
        {
            (100, 103, 97, 102),
            (102, 106, 100, 105),
            (105, 108, 102, 104),
            (104, 107, 101, 106),
            (106, 110, 104, 108),
            (108, 112, 105, 110)
        };

        foreach (var (o, h, l, c) in bars)
        {
            indicator.HistoricalData.AddBar(now, o, h, l, c);
            indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));
            now = now.AddMinutes(1);
        }

        // All values should be finite
        for (int i = 0; i < bars.Length; i++)
        {
            Assert.True(double.IsFinite(indicator.LinesSeries[0].GetValue(bars.Length - 1 - i)));
        }
    }

    [Fact]
    public void VamaIndicator_UsesOhlcForTrueRange()
    {
        // VAMA should use OHLC data for True Range calculation
        var indicator = new VamaIndicator
        {
            BaseLength = 10,
            ShortAtrPeriod = 5,
            LongAtrPeriod = 20
        };
        indicator.Initialize();

        var now = DateTime.UtcNow;

        // Add bars where High-Low range differs significantly from Close-to-Close
        indicator.HistoricalData.AddBar(now, 100, 110, 90, 100);  // TR = 20 (H-L)
        indicator.ProcessUpdate(new UpdateArgs(UpdateReason.HistoricalBar));

        indicator.HistoricalData.AddBar(now.AddMinutes(1), 100, 105, 95, 102);  // TR considering prev close
        indicator.ProcessUpdate(new UpdateArgs(UpdateReason.NewBar));

        // Both values should be finite
        Assert.True(double.IsFinite(indicator.LinesSeries[0].GetValue(0)));
        Assert.True(double.IsFinite(indicator.LinesSeries[0].GetValue(1)));
    }
}
