using System;
using System.Collections.Generic;
using System.IO;
using System.Text.Json;
using Xunit;
using QuantEngine.Core.Domain;

namespace QuantEngine.Core.Tests
{
    public class FormulaParityFixture : IDisposable
    {
        public int TotalTests = 0;
        public int PassedTests = 0;
        private readonly object _lock = new object();

        public void RegisterResult(bool passed)
        {
            lock (_lock)
            {
                TotalTests++;
                if (passed) PassedTests++;
            }
        }

        public void Dispose()
        {
            var tempDir = @"C:\Temp\data_feed\Temp";
            if (!Directory.Exists(tempDir))
            {
                Directory.CreateDirectory(tempDir);
            }

            var outputPath = Path.Combine(tempDir, "dotnet_formula_parity_v1.json");
            var result = new
            {
                gate = PassedTests == TotalTests && TotalTests >= 37 ? "PASS" : "FAIL",
                total = TotalTests,
                passed = PassedTests
            };

            File.WriteAllText(outputPath, JsonSerializer.Serialize(result, new JsonSerializerOptions { WriteIndented = true }));
        }
    }

    public class FormulaEngineTests : IClassFixture<FormulaParityFixture>
    {
        private readonly FormulaParityFixture _fixture;

        public FormulaEngineTests(FormulaParityFixture fixture)
        {
            _fixture = fixture;
        }

        [Fact]
        public void TestTimingDecisionNeutral()
        {
            bool success = false;
            try
            {
                var ctx = new Dictionary<string, object>
                {
                    { "entryModeGate", "PASS" },
                    { "entryMode", "BREAKOUT" },
                    { "leaderGate", "PASS" },
                    { "acGate", "CLEAR" },
                    { "leaderTotal", 4.0 },
                    { "flowCredit", 0.8 },
                    { "ma20Slope", 1.0 },
                    { "disparity", 0.0 },
                    { "rsi14", 50.0 },
                    { "avgTradeValue5D", 100.0 },
                    { "spreadPct", 0.1 },
                    { "priceStatus", "PRICE_OK" },
                    { "atr20", 10.0 }
                };

                var result = FormulaEngine.ComputeTimingDecision(ctx);
                Assert.NotNull(result);
                Assert.Equal("BUY_BREAKOUT_PILOT_ONLY", result.Action);
                success = true;
            }
            finally
            {
                _fixture.RegisterResult(success);
            }
        }

        [Fact]
        public void ComputeSellDecisionProducesExitTrimWhenRiskWindowIsOpen()
        {
            bool success = false;
            try
            {
                var ctx = new Dictionary<string, object>
                {
                    { "close", 100.0 },
                    { "profitPct", 31.0 },
                    { "tp1Price", 108.0 },
                    { "tp2Price", 112.0 },
                    { "timingAction", "BUY_STAGE1_READY" },
                    { "atr20", 4.0 }
                };

                var result = FormulaEngine.ComputeSellDecision(ctx);
                Assert.Equal("PROFIT_TRIM_35", result.Action);
                Assert.Equal(35, result.RatioPct);
                Assert.Equal("SIGNAL_CONFIRMED", result.Validation);
                success = true;
            }
            finally
            {
                _fixture.RegisterResult(success);
            }
        }

        [Fact]
        public void ComputeFinalDecisionPromotesSellReadyWhenSellSignalIsConfirmed()
        {
            bool success = false;
            try
            {
                var ctx = new Dictionary<string, object>
                {
                    { "sellAction", "TRIM_35" },
                    { "sellValidation", "SIGNAL_CONFIRMED" },
                    { "timingScoreEntry", 72.0 },
                    { "timingScoreExit", 15.0 }
                };

                var result = FormulaEngine.ComputeFinalDecision(ctx);
                Assert.Equal("SELL_READY", result.FinalAction);
                Assert.Equal(10, result.ActionPriority);
                Assert.Equal("RULE_ENGINE", result.DecisionSource);
                success = true;
            }
            finally
            {
                _fixture.RegisterResult(success);
            }
        }

        [Fact]
        public void ComputeCashShortfallHarnessCalculatesTargetAndShortfall()
        {
            bool success = false;
            try
            {
                var asResult = new Dictionary<string, object>
                {
                    { "settlementCashD2Krw", 10_000_000.0 }
                };
                var cashFloor = new Dictionary<string, object>
                {
                    { "minPct", 15.0 }
                };

                var result = FormulaEngine.ComputeCashShortfallHarness(asResult, 100_000_000.0, cashFloor, 6.0);
                Assert.Equal(10.0, result.CashCurrentPctD2);
                Assert.Equal(15.0, result.CashTargetPct);
                Assert.Equal(5_000_000.0, result.CashShortfallMinKrw);
                Assert.Equal(5_000_000.0, result.CashShortfallTargetKrw);
                success = true;
            }
            finally
            {
                _fixture.RegisterResult(success);
            }
        }

        [Theory]
        [InlineData(1.0, "CLEAR", "PASS")]
        [InlineData(2.0, "PULLBACK_WAIT", "WAIT")]
        [InlineData(4.0, "BLOCK_CHASE", "BLOCKED")]
        public void Formula_10_4_1_Velocity_And_10_4_3_AntiChasing(double vel, string expectedVerdict, string expectedStatus)
        {
            bool success = false;
            try
            {
                var res = AntiChasingCalculator.ComputeAntiChasing(vel);
                Assert.Equal(expectedVerdict, res.AntiChasingVerdict);
                Assert.Equal(expectedStatus, res.AntiChasingVelocityStatus);
                success = true;
            }
            finally
            {
                _fixture.RegisterResult(success);
            }
        }

        [Theory]
        [InlineData(-5.0, "NORMAL")]
        [InlineData(5.0, "BREAKEVEN_RATCHET")]
        [InlineData(15.0, "PROFIT_LOCK_10")]
        [InlineData(25.0, "PROFIT_LOCK_20")]
        [InlineData(35.0, "PROFIT_LOCK_30")]
        [InlineData(45.0, "APEX_TRAILING")]
        [InlineData(65.0, "APEX_SUPER")]
        public void Formula_10_4_2_ProfitLockStage(double profit, string expectedStage)
        {
            bool success = false;
            try
            {
                var res = ProfitLockCalculator.ClassifyProfitLockStage(profit);
                Assert.Equal(expectedStage, res);
                success = true;
            }
            finally
            {
                _fixture.RegisterResult(success);
            }
        }

        [Theory]
        [InlineData(100000.0, 100000.0, 3000.0, "PULLBACK_ZONE", "PASS")]
        [InlineData(105000.0, 100000.0, 3000.0, "ABOVE_PULLBACK_ZONE", "BLOCKED")]
        [InlineData(102000.0, 100000.0, 3000.0, "PULLBACK_ZONE", "PASS")]
        public void Formula_10_4_4_PullbackTrigger(double close, double ma, double atr, string expectedVerdict, string expectedState)
        {
            bool success = false;
            try
            {
                var res = PullbackTriggerCalculator.ComputePullbackTrigger(close, ma, atr);
                Assert.Equal(expectedVerdict, res.PullbackEntryVerdict);
                Assert.Equal(expectedState, res.PullbackState);
                success = true;
            }
            finally
            {
                _fixture.RegisterResult(success);
            }
        }

        [Theory]
        [InlineData(100000.0, 95000.0, 100000.0, "PASS")]
        [InlineData(90000.0, 95000.0, 100000.0, "INVALID_PRICE_INVERSION")]
        [InlineData(140000.0, 95000.0, 100000.0, "INVALID_UNREALISTIC_PRICE")]
        public void Formula_10_4_5_SellPriceSanity(double sell, double stop, double prev, string expectedStatus)
        {
            bool success = false;
            try
            {
                var res = SellPriceSanityChecker.CheckSellPriceSanity(sell, stop, prev);
                Assert.Equal(expectedStatus, res.SellPriceSanityStatus);
                success = true;
            }
            finally
            {
                _fixture.RegisterResult(success);
            }
        }

        [Theory]
        [InlineData(1500.0, 1)]
        [InlineData(4500.0, 5)]
        [InlineData(15000.0, 10)]
        [InlineData(45000.0, 50)]
        [InlineData(150000.0, 100)]
        [InlineData(450000.0, 500)]
        [InlineData(1000000.0, 1000)]
        [InlineData(3000000.0, 1000)]
        public void Formula_10_4_6_TickNormalizer(double price, int expectedTick)
        {
            bool success = false;
            try
            {
                int tick = KrxTickNormalizer.GetTickUnit(price);
                Assert.Equal(expectedTick, tick);
                success = true;
            }
            finally
            {
                _fixture.RegisterResult(success);
            }
        }

        [Theory]
        [InlineData(5000000.0, true)]
        [InlineData(10000000.0, false)]
        [InlineData(0.0, true)]
        public void Formula_10_4_7_CashRecoveryOptimizer(double shortfall, bool expectedShortfallMet)
        {
            bool success = false;
            try
            {
                var candidates = new List<Dictionary<string, object>>
                {
                    new Dictionary<string, object>
                    {
                        { "Ticker", "005930" },
                        { "Name", "삼성전자" },
                        { "Sell_Qty", 100 },
                        { "Sell_Limit_Price", 80000.0 },
                        { "Cash_Preserve_Ratio", 100.0 },
                        { "Cash_Preserve_Style", "FULL" }
                    }
                };

                var res = FormulaEngine.ComputeCashRecoveryOptimizer(candidates, shortfall);
                Assert.NotNull(res);
                Assert.Equal(expectedShortfallMet, res.ShortfallMet);
                success = true;
            }
            finally
            {
                _fixture.RegisterResult(success);
            }
        }

        [Theory]
        [InlineData(65.0, "APEX_SUPER")]
        [InlineData(45.0, "APEX_TRAILING")]
        [InlineData(35.0, "PROFIT_LOCK_30")]
        [InlineData(25.0, "PROFIT_LOCK_20")]
        [InlineData(15.0, "PROFIT_LOCK_10")]
        [InlineData(5.0, "BREAKEVEN_RATCHET")]
        [InlineData(-5.0, "NORMAL")]
        public void Formula_10_4_8_ProfitRatchetTiered(double profitPct, string expectedStage)
        {
            bool success = false;
            try
            {
                var res = ProfitLockCalculator.ComputeTrailingStop(
                    profitPct,
                    100000,
                    3000,
                    90000,
                    80000
                );
                Assert.Equal(expectedStage, res.RatchetStage);
                success = true;
            }
            finally
            {
                _fixture.RegisterResult(success);
            }
        }
    }
}
