using System;
using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using NT8.Core.Common.Models;
using NT8.Core.Intelligence;
using NT8.Core.Logging;
using NT8.Core.Sizing;

namespace NT8.Core.Tests.Sizing
{
    [TestClass]
    public class GradeBasedSizerTests
    {
        [TestMethod]
        public void Constructor_NullLogger_ThrowsArgumentNullException()
        {
            Assert.ThrowsException<ArgumentNullException>(delegate
            {
                new GradeBasedSizer(null, new GradeFilter());
            });
        }

        [TestMethod]
        public void Constructor_NullGradeFilter_ThrowsArgumentNullException()
        {
            Assert.ThrowsException<ArgumentNullException>(delegate
            {
                new GradeBasedSizer(new BasicLogger("test"), null);
            });
        }

        [TestMethod]
        public void CombineMultipliers_MultipliesValues()
        {
            var sizer = CreateSizer();
            var result = sizer.CombineMultipliers(1.25, 0.8);
            Assert.AreEqual(1.0, result, 0.000001);
        }

        [TestMethod]
        public void ApplyConstraints_BelowMin_ReturnsMin()
        {
            var sizer = CreateSizer();
            var result = sizer.ApplyConstraints(0, 1, 10);
            Assert.AreEqual(1, result);
        }

        [TestMethod]
        public void ApplyConstraints_AboveMax_ReturnsMax()
        {
            var sizer = CreateSizer();
            var result = sizer.ApplyConstraints(20, 1, 10);
            Assert.AreEqual(10, result);
        }

        [TestMethod]
        public void ApplyConstraints_WithinRange_ReturnsInput()
        {
            var sizer = CreateSizer();
            var result = sizer.ApplyConstraints(5, 1, 10);
            Assert.AreEqual(5, result);
        }

        [TestMethod]
        public void CalculateGradeBasedSize_RejectedGrade_ReturnsZeroContracts()
        {
            var sizer = CreateSizer();
            var baseSizer = new StubPositionSizer(4, 400.0, SizingMethod.FixedDollarRisk);

            var intent = CreateIntent();
            var context = CreateContext();
            var confluence = CreateScore(TradeGrade.C, 0.6);
            var config = CreateSizingConfig();
            var modeConfig = CreateModeConfig(RiskMode.DCP, 0.5, TradeGrade.A);

            var result = sizer.CalculateGradeBasedSize(
                intent,
                context,
                confluence,
                RiskMode.DCP,
                config,
                baseSizer,
                modeConfig);

            Assert.IsNotNull(result);
            Assert.AreEqual(0, result.Contracts);
            Assert.IsTrue(result.Calculations.ContainsKey("rejected"));
        }

        [TestMethod]
        public void CalculateGradeBasedSize_AcceptedGrade_AppliesMultipliers()
        {
            var sizer = CreateSizer();
            var baseSizer = new StubPositionSizer(4, 400.0, SizingMethod.FixedDollarRisk);

            var intent = CreateIntent();
            var context = CreateContext();
            var confluence = CreateScore(TradeGrade.A, 0.85);
            var config = CreateSizingConfig();
            var modeConfig = CreateModeConfig(RiskMode.ECP, 1.5, TradeGrade.B);

            var result = sizer.CalculateGradeBasedSize(
                intent,
                context,
                confluence,
                RiskMode.ECP,
                config,
                baseSizer,
                modeConfig);

            // Base contracts = 4
            // Grade multiplier (ECP, A) = 1.25
            // Mode multiplier = 1.5
            // Raw = 7.5, floor => 7
            Assert.AreEqual(7, result.Contracts);
            Assert.IsTrue(result.Calculations.ContainsKey("combined_multiplier"));
        }

        [TestMethod]
        public void CalculateGradeBasedSize_RespectsMaxContracts()
        {
            var sizer = CreateSizer();
            var baseSizer = new StubPositionSizer(8, 800.0, SizingMethod.FixedDollarRisk);

            var intent = CreateIntent();
            var context = CreateContext();
            var confluence = CreateScore(TradeGrade.APlus, 0.92);
            var config = new SizingConfig(SizingMethod.FixedDollarRisk, 1, 10, 500.0, new Dictionary<string, object>());
            var modeConfig = CreateModeConfig(RiskMode.ECP, 1.5, TradeGrade.B);

            var result = sizer.CalculateGradeBasedSize(
                intent,
                context,
                confluence,
                RiskMode.ECP,
                config,
                baseSizer,
                modeConfig);

            // 8 * 1.5 * 1.5 = 18 -> clamp 10
            Assert.AreEqual(10, result.Contracts);
        }

        [TestMethod]
        public void CalculateGradeBasedSize_RespectsMinContracts_WhenAccepted()
        {
            var sizer = CreateSizer();
            var baseSizer = new StubPositionSizer(1, 100.0, SizingMethod.FixedDollarRisk);

            var intent = CreateIntent();
            var context = CreateContext();
            var confluence = CreateScore(TradeGrade.C, 0.61);
            var config = new SizingConfig(SizingMethod.FixedDollarRisk, 2, 10, 500.0, new Dictionary<string, object>());
            var modeConfig = CreateModeConfig(RiskMode.PCP, 1.0, TradeGrade.C);

            var result = sizer.CalculateGradeBasedSize(
                intent,
                context,
                confluence,
                RiskMode.PCP,
                config,
                baseSizer,
                modeConfig);

            Assert.AreEqual(2, result.Contracts);
        }

        [TestMethod]
        public void CalculateGradeBasedSize_NullInputs_Throw()
        {
            var sizer = CreateSizer();
            var baseSizer = new StubPositionSizer(1, 100.0, SizingMethod.FixedDollarRisk);
            var intent = CreateIntent();
            var context = CreateContext();
            var confluence = CreateScore(TradeGrade.A, 0.8);
            var config = CreateSizingConfig();
            var modeConfig = CreateModeConfig(RiskMode.PCP, 1.0, TradeGrade.C);

            Assert.ThrowsException<ArgumentNullException>(delegate
            {
                sizer.CalculateGradeBasedSize(null, context, confluence, RiskMode.PCP, config, baseSizer, modeConfig);
            });

            Assert.ThrowsException<ArgumentNullException>(delegate
            {
                sizer.CalculateGradeBasedSize(intent, null, confluence, RiskMode.PCP, config, baseSizer, modeConfig);
            });

            Assert.ThrowsException<ArgumentNullException>(delegate
            {
                sizer.CalculateGradeBasedSize(intent, context, null, RiskMode.PCP, config, baseSizer, modeConfig);
            });
        }

        private static GradeBasedSizer CreateSizer()
        {
            return new GradeBasedSizer(new BasicLogger("GradeBasedSizerTests"), new GradeFilter());
        }

        private static StrategyIntent CreateIntent()
        {
            return new StrategyIntent(
                "ES",
                OrderSide.Buy,
                OrderType.Market,
                null,
                8,
                16,
                0.8,
                "Test intent",
                new Dictionary<string, object>());
        }

        private static StrategyContext CreateContext()
        {
            return new StrategyContext(
                "ES",
                DateTime.UtcNow,
                new Position("ES", 0, 0, 0, 0, DateTime.UtcNow),
                new AccountInfo(100000, 100000, 0, 0, DateTime.UtcNow),
                new MarketSession(DateTime.Today.AddHours(9.5), DateTime.Today.AddHours(16), true, "RTH"),
                new Dictionary<string, object>());
        }

        private static ConfluenceScore CreateScore(TradeGrade grade, double weighted)
        {
            var factors = new List<ConfluenceFactor>();
            factors.Add(new ConfluenceFactor(FactorType.Setup, "Setup", weighted, 1.0, "test", new Dictionary<string, object>()));

            return new ConfluenceScore(
                weighted,
                weighted,
                grade,
                factors,
                DateTime.UtcNow,
                new Dictionary<string, object>());
        }

        private static SizingConfig CreateSizingConfig()
        {
            return new SizingConfig(SizingMethod.FixedDollarRisk, 1, 10, 500.0, new Dictionary<string, object>());
        }

        private static RiskModeConfig CreateModeConfig(RiskMode mode, double sizeMultiplier, TradeGrade minGrade)
        {
            return new RiskModeConfig(mode, sizeMultiplier, minGrade, 1000.0, 3, false, new Dictionary<string, object>());
        }

        private class StubPositionSizer : IPositionSizer
        {
            private readonly int _contracts;
            private readonly double _risk;
            private readonly SizingMethod _method;

            public StubPositionSizer(int contracts, double risk, SizingMethod method)
            {
                _contracts = contracts;
                _risk = risk;
                _method = method;
            }

            public SizingResult CalculateSize(StrategyIntent intent, StrategyContext context, SizingConfig config)
            {
                return new SizingResult(_contracts, _risk, _method, new Dictionary<string, object>());
            }

            public SizingMetadata GetMetadata()
            {
                return new SizingMetadata("Stub", "Stub sizer", new List<string>());
            }
        }
    }
}
