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

namespace QuantEngine.Core.Tests.ParityTests
{
    public class ParityFixture : 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()
        {
            string? tempDir = null;
            var current = new DirectoryInfo(AppContext.BaseDirectory);
            while (current != null)
            {
                if (Directory.Exists(Path.Combine(current.FullName, ".git")))
                {
                    tempDir = Path.Combine(current.FullName, "Temp");
                    break;
                }
                current = current.Parent;
            }

            if (tempDir == null)
            {
                tempDir = Path.Combine(Directory.GetCurrentDirectory(), "Temp");
            }

            if (!Directory.Exists(tempDir))
            {
                Directory.CreateDirectory(tempDir);
            }

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

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

    public class DomainParityTests : IClassFixture<ParityFixture>
    {
        private readonly ParityFixture _fixture;

        public DomainParityTests(ParityFixture fixture)
        {
            _fixture = fixture;
        }

        [Theory]
        [InlineData(100000.0, 3000.0, 100000.0, 2.0, 94000.0)]
        [InlineData(100000.0, 3000.0, 100000.0, 1.5, 95500.0)]
        [InlineData(50000.0, 1500.0, 50000.0, 2.0, 47000.0)]
        [InlineData(50000.0, null, null, null, 46000.0)]
        [InlineData(10000.0, 500.0, 10000.0, null, 9250.0)] // Fix expected value to 9250.0 based on 1.5x ATR multiplier (ATR 5.0% < 8.0%)
        [InlineData(80000.0, 2000.0, 80000.0, 2.0, 76000.0)]
        [InlineData(200000.0, 5000.0, 200000.0, 1.5, 192500.0)]
        [InlineData(150000.0, 4000.0, 150000.0, 2.0, 142000.0)]
        [InlineData(300000.0, 8000.0, 300000.0, 1.5, 288000.0)]
        [InlineData(120000.0, 3000.0, 120000.0, 2.0, 114000.0)]
        public void StopPriceParity_MatchesPython(double entry, double? atr, double? current, double? mult, double expectedStop)
        {
            bool success = false;
            try
            {
                var res = ExitDecisions.ComputeStopPriceCore(entry, atr, current, mult);
                Assert.NotNull(res.StopPrice);
                Assert.InRange(res.StopPrice.Value, expectedStop * 0.9999, expectedStop * 1.0001);
                success = true;
            }
            finally
            {
                _fixture.RegisterResult(success);
            }
        }

        [Fact]
        public void StopPriceParity_HandlesMissingEntryPrice()
        {
            bool success = false;
            try
            {
                var res = ExitDecisions.ComputeStopPriceCore(null, 3000.0, 100000.0, 2.0);
                Assert.Null(res.StopPrice);
                Assert.Equal("NO_STOP_PRICE", res.StopPriceStatus);
                Assert.Contains("entry_price", res.DataMissing);
                success = true;
            }
            finally
            {
                _fixture.RegisterResult(success);
            }
        }

        [Fact]
        public void StopPriceParity_HandlesMissingAtrAndMultiplier()
        {
            bool success = false;
            try
            {
                var res = ExitDecisions.ComputeStopPriceCore(100000.0, null, null, null);
                Assert.Equal(92000.0, res.StopPrice);
                Assert.Equal("DATA_MISSING — 하네스 업데이트 필요", res.StopPriceStatus);
                Assert.Contains("atr20", res.DataMissing);
                success = true;
            }
            finally
            {
                _fixture.RegisterResult(success);
            }
        }

        [Theory]
        [InlineData("STOP_OR_TIME_EXIT_READY", 0, "RISK_ON", 0.0, false, 9999, "EXIT_100")]
        [InlineData("NORMAL", 4, "RISK_ON", 0.0, false, 9999, "EXIT_100")]
        [InlineData("NORMAL", 1, "RISK_OFF", 0.0, false, 9999, "REGIME_TRIM_50")]
        [InlineData("NORMAL", 1, "RISK_OFF_CANDIDATE", 0.0, false, 9999, "REGIME_TRIM_50")]
        [InlineData("NORMAL", 1, "RISK_ON", 75.0, false, 9999, "TRIM_70")]
        [InlineData("NORMAL", 3, "RISK_ON", 0.0, false, 9999, "TRIM_70")]
        [InlineData("NORMAL", 1, "RISK_ON", 0.0, true, 9999, "TRIM_50")]
        [InlineData("NORMAL", 2, "RISK_ON", 0.0, false, 9999, "TRIM_50")]
        [InlineData("NORMAL", 1, "RISK_ON", 50.0, false, 9999, "TRIM_50")]
        [InlineData("NORMAL", 0, "RISK_ON", 15.0, false, 9999, "TAKE_PROFIT_TIER1")]
        [InlineData("NORMAL", 0, "RISK_ON", 0.0, false, 0, "TIME_EXIT_100")]
        [InlineData("NORMAL", 0, "RISK_ON", 0.0, false, 9999, "REVIEW_HUMAN")]
        public void StopActionLadderParity_MatchesPython(
            string timingAction,
            int rwPartial,
            string regime,
            double param1,
            bool trailingStop,
            int daysToTimeStop,
            string expectedAction)
        {
            bool success = false;
            try
            {
                var ctx = new Dictionary<string, object>
                {
                    { "timingAction", timingAction },
                    { "rw_partial", rwPartial },
                    { "REGIME_PRELIM", regime },
                    { "trailingStopBreach", trailingStop },
                    { "daysToTimeStop", daysToTimeStop }
                };

                if (expectedAction == "TAKE_PROFIT_TIER1")
                {
                    ctx["profitPct"] = param1;
                }
                else
                {
                    ctx["timingExitScore"] = param1;
                }

                var res = ExitDecisions.ComputeStopActionLadder(ctx);
                Assert.Equal(expectedAction, res.Action);
                success = true;
            }
            finally
            {
                _fixture.RegisterResult(success);
            }
        }

        [Theory]
        [InlineData("EVENT_SHOCK", 5.0, 3.5)]
        [InlineData("RISK_OFF", 7.0, 5.0)]
        [InlineData("SECULAR_LEADER_RISK_ON", 13.0, 9.0)]
        public void HeatThresholdParity_MatchesPython(string regime, double expectedHard, double expectedHalve)
        {
            bool success = false;
            try
            {
                var res = ExitDecisions.ComputeDynamicHeatThresholds(regime);
                Assert.Equal(expectedHard, res.HardBlock);
                Assert.Equal(expectedHalve, res.Halve);
                success = true;
            }
            finally
            {
                _fixture.RegisterResult(success);
            }
        }

        [Fact]
        public void HeatThresholdParity_DefaultsToBaseThreshold()
        {
            bool success = false;
            try
            {
                var res = ExitDecisions.ComputeDynamicHeatThresholds("");
                Assert.Equal(10.0, res.HardBlock);
                Assert.Equal(7.0, res.Halve);
                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 ProfitLockParity_MatchesPython(double profitPct, string expectedStage)
        {
            bool success = false;
            try
            {
                var stage = ProfitLockCalculator.ClassifyProfitLockStage(profitPct);
                Assert.Equal(expectedStage, stage);
                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 KrxTickParity_MatchesPython(double price, int expectedTick)
        {
            bool success = false;
            try
            {
                int tick = KrxTickNormalizer.GetTickUnit(price);
                Assert.Equal(expectedTick, tick);
                success = true;
            }
            finally
            {
                _fixture.RegisterResult(success);
            }
        }

        [Fact]
        public void TimingDecisionParity_RejectsInvalidMarketData()
        {
            bool success = false;
            try
            {
                var ctx = new Dictionary<string, object>
                {
                    { "priceStatus", "PRICE_MISSING" }
                };

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