using PolyTrader.Modules.ResolutionFarming.Logic;
using Xunit;
using static PolyTrader.Modules.ResolutionFarming.Logic.FarmingRiskEngine;

namespace PolyTrader.Tests
{
    /// <summary>
    /// Sicherheitsnetz für die geldkritische Risiko-Logik des ResolutionFarming (Netto-Edge nach Fees,
    /// Positionsgröße unter Cluster-/Exposure-Limits, Kill-Switch). „Das Risikomodell IST die Strategie."
    /// </summary>
    public class FarmingRiskEngineTests
    {
        // ----- NetEdge / NetEdgePct -----

        [Fact]
        public void NetEdge_subtracts_ask_and_per_share_fee()
        {
            // Ask 0.95, Fee 100 bps: (1-0.95) - 0.95*0.01 = 0.05 - 0.0095 = 0.0405
            Assert.Equal(0.0405m, NetEdge(0.95m, 100));
            // Fee 0 (Maker): reine Marge
            Assert.Equal(0.05m, NetEdge(0.95m, 0));
        }

        [Theory]
        [InlineData(0.0)]    // ungültig
        [InlineData(1.0)]    // ungültig
        [InlineData(1.5)]    // ungültig
        public void NetEdge_invalid_ask_is_negative(double ask)
        {
            Assert.True(NetEdge((decimal)ask, 0) < 0m);
        }

        // ----- HasEdge -----

        [Theory]
        [InlineData(0.95, 0, 1.5, true)]     // 5.26% > 1.5%
        [InlineData(0.95, 0, 6.0, false)]    // 5.26% < 6%
        [InlineData(0.99, 180, 1.5, false)]  // Krypto-Fee frisst die Marge -> negativ
        [InlineData(0.90, 75, 1.5, true)]    // Sports-Fee, komfortabler Edge
        public void HasEdge_respects_threshold_after_fees(double ask, int feeBps, double minEdgePct, bool expected)
        {
            Assert.Equal(expected, HasEdge((decimal)ask, feeBps, (decimal)minEdgePct));
        }

        // ----- AllowedPositionUsd -----

        [Fact]
        public void AllowedPosition_is_min_of_all_limits()
        {
            // bankroll 1000: cluster 10% -> 100, total 60% -> 600, market 25. Kein Bestand -> 25.
            Assert.Equal(25m, AllowedPositionUsd(25m, 10m, 60m, 1000m, 0m, 0m, 0m));
        }

        [Fact]
        public void AllowedPosition_market_cap_binds()
        {
            // Bestand im Markt 20 von 25 -> nur 5 Rest.
            Assert.Equal(5m, AllowedPositionUsd(25m, 10m, 60m, 1000m, 0m, 0m, 20m));
        }

        [Fact]
        public void AllowedPosition_cluster_cap_binds()
        {
            // Cluster-Exposure 95 von 100 -> 5 Rest.
            Assert.Equal(5m, AllowedPositionUsd(25m, 10m, 60m, 1000m, 0m, 95m, 0m));
        }

        [Fact]
        public void AllowedPosition_total_cap_binds()
        {
            // Gesamt-Exposure 590 von 600 -> 10 Rest.
            Assert.Equal(10m, AllowedPositionUsd(25m, 10m, 60m, 1000m, 590m, 0m, 0m));
        }

        [Fact]
        public void AllowedPosition_exhausted_or_zero_bankroll_returns_zero()
        {
            Assert.Equal(0m, AllowedPositionUsd(25m, 10m, 60m, 1000m, 0m, 0m, 25m)); // Markt voll
            Assert.Equal(0m, AllowedPositionUsd(25m, 10m, 60m, 0m, 0m, 0m, 0m));     // keine Bankroll
            Assert.Equal(0m, AllowedPositionUsd(25m, 10m, 60m, 1000m, 600m, 0m, 0m)); // Gesamt voll
        }

        // ----- Kill-Switch / Tages-Drossel -----

        [Theory]
        [InlineData(-10.0, 10.0, true)]   // genau an der Schwelle
        [InlineData(-11.0, 10.0, true)]   // darunter
        [InlineData(-9.0, 10.0, false)]   // darüber
        [InlineData(-50.0, 0.0, false)]   // deaktiviert (0)
        public void ShouldKill_on_daily_loss(double pnl, double threshold, bool expected)
        {
            Assert.Equal(expected, ShouldKill((decimal)pnl, (decimal)threshold));
        }

        [Theory]
        [InlineData(19, 20, false)]
        [InlineData(20, 20, true)]
        [InlineData(21, 20, true)]
        public void DailyLimitReached_counts_new_positions(int today, int max, bool expected)
        {
            Assert.Equal(expected, DailyLimitReached(today, max));
        }
    }
}
