using PolyTrader.Modules.CopyTrading.Logic;
using Xunit;
using static PolyTrader.Modules.CopyTrading.Logic.SellLogic;

namespace PolyTrader.Tests
{
    /// <summary>
    /// Sicherheitsnetz für die SELL-Logik: Proportionalität (aktuelles Verhalten, Phase-2-Umbau)
    /// und die Eskalationsleiter (Phase 0.1 – Preis-Stufen, Floor-Clamping).
    /// </summary>
    public class SellLogicTests
    {
        // ---------------- SellProportion ----------------

        [Fact]
        public void SellProportion_is_share_over_total_before_sell()
        {
            // Master hält noch 300, verkauft 200 -> 200/500 = 0.40
            Assert.Equal(0.40m, SellProportion(masterSharesRemaining: 300m, signalSize: 200m));
        }

        [Fact]
        public void SellProportion_small_partial_is_below_threshold()
        {
            // 498 Rest, 2 verkauft -> 0.004 < 0.30
            var ratio = SellProportion(498m, 2m);
            Assert.True(ratio < MinSignificantSellRatio);
        }

        [Fact]
        public void SellProportion_full_exit_is_one()
        {
            Assert.Equal(1.0m, SellProportion(0m, 10m));
        }

        [Theory]
        [InlineData(0, 0)]     // gar nichts
        [InlineData(-5, 5)]    // Denominator 0
        [InlineData(-10, 5)]   // Denominator negativ
        public void SellProportion_guards_non_positive_denominator(double remaining, double signal)
        {
            Assert.Equal(0m, SellProportion((decimal)remaining, (decimal)signal));
        }

        // ---------------- Eskalationsleiter ----------------

        [Fact]
        public void FirstLimit_normal_trader_applies_percentage_discount()
        {
            // 0.99 * (1 - 2%) = 0.9702
            Assert.Equal(0.9702m, FirstLimit(0.99m, isHf: false, maxPriceDifferencePct: 2m));
        }

        [Fact]
        public void FirstLimit_hf_trader_applies_fixed_half_cent_discount()
        {
            Assert.Equal(0.985m, FirstLimit(0.99m, isHf: true, maxPriceDifferencePct: 99m));
        }

        [Fact]
        public void Floor_is_reference_minus_floor_pct()
        {
            // 0.99 * (1 - 15%) = 0.8415
            Assert.Equal(0.8415m, Floor(0.99m, 15m));
        }

        [Fact]
        public void NextPrice_steps_down_relative_when_above_floor()
        {
            // 0.90 * (1 - 3%) = 0.873, über Floor 0.80
            Assert.Equal(0.873m, NextPrice(0.90m, stepPct: 3m, floor: 0.80m));
        }

        [Fact]
        public void NextPrice_clamps_to_floor()
        {
            // 0.82 * 0.97 = 0.7954 -> unter Floor -> 0.80
            Assert.Equal(0.80m, NextPrice(0.82m, 3m, 0.80m));
        }

        [Fact]
        public void IsAtFloor_is_inclusive()
        {
            Assert.True(IsAtFloor(0.80m, 0.80m));
            Assert.True(IsAtFloor(0.79m, 0.80m));
            Assert.False(IsAtFloor(0.81m, 0.80m));
        }

        [Theory]
        [InlineData(true, 20)]   // HF-Trader: schnelles Nachpreisen
        [InlineData(false, 120)] // Standard
        public void LadderIntervalSeconds_hf_is_faster(bool isHf, int expected)
        {
            Assert.Equal(expected, LadderIntervalSeconds(isHf));
        }

        [Theory]
        [InlineData(0.50, 0.55, 10, true)]    // genau an der Schwelle (entry*1.10)
        [InlineData(0.50, 0.60, 10, true)]    // darüber
        [InlineData(0.50, 0.54, 10, false)]   // darunter
        [InlineData(0.50, 0.99, 9999, false)] // Default 9999 -> nie erreicht (inaktiv)
        [InlineData(0.50, 0.99, 0, false)]    // 0 -> deaktiviert
        public void IsProfitTargetReached_threshold(double entry, double current, double pct, bool expected)
        {
            Assert.Equal(expected, IsProfitTargetReached((decimal)current, (decimal)entry, (decimal)pct));
        }

        [Fact]
        public void IsProfitTargetReached_zero_entry_is_false()
        {
            Assert.False(IsProfitTargetReached(0.9m, 0m, 10m));
        }

        // ---------------- SharesToSell (Phase 2) ----------------

        [Fact]
        public void SharesToSell_mirrors_partial_ratio()
        {
            // 40 % von 100, Rest 60 -> Teilverkauf 40
            Assert.Equal(40m, SharesToSell(ourShares: 100m, sellRatio: 0.40m, minShares: 5.5m, minSellRatioPct: 10m));
        }

        [Fact]
        public void SharesToSell_ignores_noise_below_min_ratio()
        {
            Assert.Equal(0m, SharesToSell(100m, 0.05m, 5.5m, 10m));
        }

        [Fact]
        public void SharesToSell_full_exit_when_master_fully_out()
        {
            Assert.Equal(100m, SharesToSell(100m, 1.0m, 5.5m, 10m));
        }

        [Fact]
        public void SharesToSell_full_exit_when_partial_would_be_dust()
        {
            // 30 % von 10 = 3 Shares < Minimum -> Voll-Exit
            Assert.Equal(10m, SharesToSell(10m, 0.30m, 5.5m, 10m));
        }

        [Fact]
        public void SharesToSell_full_exit_when_remainder_would_be_dust()
        {
            // 98 % von 100 -> Rest 2 < Minimum -> Voll-Exit
            Assert.Equal(100m, SharesToSell(100m, 0.98m, 5.5m, 10m));
        }

        [Fact]
        public void SharesToSell_zero_position_is_zero()
        {
            Assert.Equal(0m, SharesToSell(0m, 0.5m, 5.5m, 10m));
        }

        [Fact]
        public void SharesToSell_exactly_at_min_ratio_is_not_ignored()
        {
            Assert.Equal(10m, SharesToSell(100m, 0.10m, 5.5m, 10m));
        }

        [Fact]
        public void Ladder_walks_down_in_steps_until_floor()
        {
            // Simuliert den Service: von Startlimit in 3%-Schritten bis zum Floor, dann Stopp.
            decimal reference = 0.99m;
            decimal price = FirstLimit(reference, isHf: false, maxPriceDifferencePct: 2m); // 0.9702
            decimal floor = Floor(reference, 15m); // 0.8415

            int steps = 0;
            while (!IsAtFloor(price, floor) && steps < 100)
            {
                price = NextPrice(price, LadderStepPct, floor);
                steps++;
            }

            Assert.True(IsAtFloor(price, floor));
            Assert.Equal(floor, price);          // endet exakt auf dem Floor (geclamped)
            Assert.InRange(steps, 1, 20);        // terminiert in wenigen Schritten
        }

        // ----- RoundToTick (H4) -----

        [Theory]
        [InlineData(0.9702, 0.970)]   // typischer Startlimit-Wert -> 3 Dezimalen
        [InlineData(0.84150, 0.842)]  // AwayFromZero an der Hälfte
        [InlineData(0.5, 0.500)]
        [InlineData(0.123456, 0.123)]
        public void RoundToTick_rounds_to_three_decimals(double input, double expected)
        {
            Assert.Equal((decimal)expected, RoundToTick((decimal)input));
        }

        [Fact]
        public void RoundToTick_makes_usdc_over_price_yield_exact_size()
        {
            // Kern von H4: mit gerundetem Preis geht size = usdc / price exakt auf.
            decimal size = 100m;
            decimal price = RoundToTick(NextPrice(0.9702m, LadderStepPct, 0.80m));
            decimal usdc = size * price;
            Assert.Equal(size, usdc / price);
        }

        // ----- IsLadderConfigInverted (Slice 6) -----

        [Theory]
        [InlineData(2.0, 15.0, false)]   // Default: Abstand < Floor -> ok
        [InlineData(15.0, 15.0, true)]   // gleich -> Start am Floor
        [InlineData(20.0, 15.0, true)]   // Abstand > Floor -> invertiert
        public void IsLadderConfigInverted_flags_buydiff_ge_floor(double maxDiff, double floor, bool expected)
        {
            Assert.Equal(expected, IsLadderConfigInverted((decimal)maxDiff, (decimal)floor));
        }
    }
}
