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

namespace NT8.Core.Tests.Intelligence
{
    [TestClass]
    public class ConfluenceScorerTests
    {
        [TestMethod]
        public void Constructor_NullLogger_ThrowsArgumentNullException()
        {
            Assert.ThrowsException<ArgumentNullException>(delegate
            {
                new ConfluenceScorer(null, 100);
            });
        }

        [TestMethod]
        public void Constructor_InvalidHistory_ThrowsArgumentException()
        {
            Assert.ThrowsException<ArgumentException>(delegate
            {
                new ConfluenceScorer(new BasicLogger("test"), 0);
            });
        }

        [TestMethod]
        public void MapScoreToGrade_At090_ReturnsAPlus()
        {
            var scorer = CreateScorer();
            var grade = scorer.MapScoreToGrade(0.90);
            Assert.AreEqual(TradeGrade.APlus, grade);
        }

        [TestMethod]
        public void MapScoreToGrade_At080_ReturnsA()
        {
            var scorer = CreateScorer();
            var grade = scorer.MapScoreToGrade(0.80);
            Assert.AreEqual(TradeGrade.A, grade);
        }

        [TestMethod]
        public void MapScoreToGrade_At070_ReturnsB()
        {
            var scorer = CreateScorer();
            var grade = scorer.MapScoreToGrade(0.70);
            Assert.AreEqual(TradeGrade.B, grade);
        }

        [TestMethod]
        public void MapScoreToGrade_At060_ReturnsC()
        {
            var scorer = CreateScorer();
            var grade = scorer.MapScoreToGrade(0.60);
            Assert.AreEqual(TradeGrade.C, grade);
        }

        [TestMethod]
        public void MapScoreToGrade_At050_ReturnsD()
        {
            var scorer = CreateScorer();
            var grade = scorer.MapScoreToGrade(0.50);
            Assert.AreEqual(TradeGrade.D, grade);
        }

        [TestMethod]
        public void MapScoreToGrade_Below050_ReturnsF()
        {
            var scorer = CreateScorer();
            var grade = scorer.MapScoreToGrade(0.49);
            Assert.AreEqual(TradeGrade.F, grade);
        }

        [TestMethod]
        public void CalculateScore_NullIntent_ThrowsArgumentNullException()
        {
            var scorer = CreateScorer();
            var context = CreateContext();
            var bar = CreateBar();
            var factors = CreateFactors();

            Assert.ThrowsException<ArgumentNullException>(delegate
            {
                scorer.CalculateScore(null, context, bar, factors);
            });
        }

        [TestMethod]
        public void CalculateScore_NullContext_ThrowsArgumentNullException()
        {
            var scorer = CreateScorer();
            var intent = CreateIntent();
            var bar = CreateBar();
            var factors = CreateFactors();

            Assert.ThrowsException<ArgumentNullException>(delegate
            {
                scorer.CalculateScore(intent, null, bar, factors);
            });
        }

        [TestMethod]
        public void CalculateScore_NullBar_ThrowsArgumentNullException()
        {
            var scorer = CreateScorer();
            var intent = CreateIntent();
            var context = CreateContext();
            var factors = CreateFactors();

            Assert.ThrowsException<ArgumentNullException>(delegate
            {
                scorer.CalculateScore(intent, context, null, factors);
            });
        }

        [TestMethod]
        public void CalculateScore_NullFactors_ThrowsArgumentNullException()
        {
            var scorer = CreateScorer();
            var intent = CreateIntent();
            var context = CreateContext();
            var bar = CreateBar();

            Assert.ThrowsException<ArgumentNullException>(delegate
            {
                scorer.CalculateScore(intent, context, bar, null);
            });
        }

        [TestMethod]
        public void CalculateScore_EmptyFactors_ReturnsZeroScoreAndF()
        {
            var scorer = CreateScorer();
            var intent = CreateIntent();
            var context = CreateContext();
            var bar = CreateBar();

            var result = scorer.CalculateScore(intent, context, bar, new List<IFactorCalculator>());

            Assert.IsNotNull(result);
            Assert.AreEqual(0.0, result.RawScore, 0.000001);
            Assert.AreEqual(0.0, result.WeightedScore, 0.000001);
            Assert.AreEqual(TradeGrade.F, result.Grade);
        }

        [TestMethod]
        public void CalculateScore_SingleFactor_UsesFactorScore()
        {
            var scorer = CreateScorer();
            var intent = CreateIntent();
            var context = CreateContext();
            var bar = CreateBar();
            var factors = new List<IFactorCalculator>();
            factors.Add(new FixedFactorCalculator(FactorType.Setup, 0.75, 1.0));

            var result = scorer.CalculateScore(intent, context, bar, factors);

            Assert.AreEqual(0.75, result.RawScore, 0.000001);
            Assert.AreEqual(0.75, result.WeightedScore, 0.000001);
            Assert.AreEqual(TradeGrade.B, result.Grade);
            Assert.AreEqual(1, result.Factors.Count);
        }

        [TestMethod]
        public void CalculateScore_MultipleFactors_CalculatesWeightedAverage()
        {
            var scorer = CreateScorer();
            var intent = CreateIntent();
            var context = CreateContext();
            var bar = CreateBar();
            var factors = new List<IFactorCalculator>();
            factors.Add(new FixedFactorCalculator(FactorType.Setup, 1.0, 1.0));
            factors.Add(new FixedFactorCalculator(FactorType.Trend, 0.5, 1.0));
            factors.Add(new FixedFactorCalculator(FactorType.Timing, 0.0, 1.0));

            var result = scorer.CalculateScore(intent, context, bar, factors);

            Assert.AreEqual(0.5, result.RawScore, 0.000001);
            Assert.AreEqual(0.5, result.WeightedScore, 0.000001);
            Assert.AreEqual(TradeGrade.D, result.Grade);
            Assert.AreEqual(3, result.Factors.Count);
        }

        [TestMethod]
        public void UpdateFactorWeights_AppliesOverrides()
        {
            var scorer = CreateScorer();
            var intent = CreateIntent();
            var context = CreateContext();
            var bar = CreateBar();

            var updates = new Dictionary<FactorType, double>();
            updates.Add(FactorType.Setup, 2.0);
            updates.Add(FactorType.Trend, 1.0);
            scorer.UpdateFactorWeights(updates);

            var factors = new List<IFactorCalculator>();
            factors.Add(new FixedFactorCalculator(FactorType.Setup, 1.0, 1.0));
            factors.Add(new FixedFactorCalculator(FactorType.Trend, 0.0, 1.0));

            var result = scorer.CalculateScore(intent, context, bar, factors);

            Assert.AreEqual(0.666666, result.WeightedScore, 0.0005);
        }

        [TestMethod]
        public void UpdateFactorWeights_InvalidWeight_ThrowsArgumentException()
        {
            var scorer = CreateScorer();
            var updates = new Dictionary<FactorType, double>();
            updates.Add(FactorType.Setup, 0.0);

            Assert.ThrowsException<ArgumentException>(delegate
            {
                scorer.UpdateFactorWeights(updates);
            });
        }

        [TestMethod]
        public void GetHistoricalStats_Empty_ReturnsDefaults()
        {
            var scorer = CreateScorer();

            var stats = scorer.GetHistoricalStats();

            Assert.IsNotNull(stats);
            Assert.AreEqual(0, stats.TotalCalculations);
            Assert.AreEqual(0.0, stats.AverageWeightedScore, 0.000001);
            Assert.AreEqual(0.0, stats.AverageRawScore, 0.000001);
        }

        [TestMethod]
        public void GetHistoricalStats_AfterScores_ReturnsCounts()
        {
            var scorer = CreateScorer();
            var intent = CreateIntent();
            var context = CreateContext();
            var bar = CreateBar();

            var factors1 = new List<IFactorCalculator>();
            factors1.Add(new FixedFactorCalculator(FactorType.Setup, 0.90, 1.0));

            var factors2 = new List<IFactorCalculator>();
            factors2.Add(new FixedFactorCalculator(FactorType.Setup, 0.40, 1.0));

            scorer.CalculateScore(intent, context, bar, factors1);
            scorer.CalculateScore(intent, context, bar, factors2);

            var stats = scorer.GetHistoricalStats();

            Assert.AreEqual(2, stats.TotalCalculations);
            Assert.IsTrue(stats.BestWeightedScore >= stats.WorstWeightedScore);
            Assert.IsTrue(stats.GradeDistribution[TradeGrade.APlus] >= 0);
            Assert.IsTrue(stats.GradeDistribution[TradeGrade.F] >= 0);
        }

        [TestMethod]
        public void CalculateScore_CurrentBarOverload_UsesContextCustomData()
        {
            var scorer = CreateScorer();
            var intent = CreateIntent();
            var context = CreateContext();
            var bar = CreateBar();
            context.CustomData["current_bar"] = bar;

            var factors = CreateFactors();
            var result = scorer.CalculateScore(intent, context, factors);

            Assert.IsNotNull(result);
            Assert.IsTrue(result.WeightedScore >= 0.0);
            Assert.IsTrue(result.WeightedScore <= 1.0);
        }

        [TestMethod]
        public void CalculateScore_CurrentBarOverload_MissingBar_ThrowsArgumentException()
        {
            var scorer = CreateScorer();
            var intent = CreateIntent();
            var context = CreateContext();
            var factors = CreateFactors();

            Assert.ThrowsException<ArgumentException>(delegate
            {
                scorer.CalculateScore(intent, context, factors);
            });
        }

        [TestMethod]
        public void CalculateScore_HistoryRespectsMaxCapacity()
        {
            var scorer = new ConfluenceScorer(new BasicLogger("test"), 2);
            var intent = CreateIntent();
            var context = CreateContext();
            var bar = CreateBar();

            var factorsA = new List<IFactorCalculator>();
            factorsA.Add(new FixedFactorCalculator(FactorType.Setup, 0.9, 1.0));
            var factorsB = new List<IFactorCalculator>();
            factorsB.Add(new FixedFactorCalculator(FactorType.Setup, 0.8, 1.0));
            var factorsC = new List<IFactorCalculator>();
            factorsC.Add(new FixedFactorCalculator(FactorType.Setup, 0.7, 1.0));

            scorer.CalculateScore(intent, context, bar, factorsA);
            scorer.CalculateScore(intent, context, bar, factorsB);
            scorer.CalculateScore(intent, context, bar, factorsC);

            var stats = scorer.GetHistoricalStats();
            Assert.AreEqual(2, stats.TotalCalculations);
        }

        private static ConfluenceScorer CreateScorer()
        {
            return new ConfluenceScorer(new BasicLogger("ConfluenceScorerTests"), 100);
        }

        private static StrategyIntent CreateIntent()
        {
            return new StrategyIntent(
                "ES",
                OrderSide.Buy,
                OrderType.Market,
                null,
                8,
                16,
                0.8,
                "Test",
                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 BarData CreateBar()
        {
            return new BarData("ES", DateTime.UtcNow, 5000, 5005, 4998, 5002, 1000, TimeSpan.FromMinutes(1));
        }

        private static List<IFactorCalculator> CreateFactors()
        {
            var factors = new List<IFactorCalculator>();
            factors.Add(new FixedFactorCalculator(FactorType.Setup, 0.8, 1.0));
            factors.Add(new FixedFactorCalculator(FactorType.Trend, 0.7, 1.0));
            factors.Add(new FixedFactorCalculator(FactorType.Volatility, 0.6, 1.0));
            return factors;
        }

        private class FixedFactorCalculator : IFactorCalculator
        {
            private readonly FactorType _type;
            private readonly double _score;
            private readonly double _weight;

            public FixedFactorCalculator(FactorType type, double score, double weight)
            {
                _type = type;
                _score = score;
                _weight = weight;
            }

            public FactorType Type
            {
                get { return _type; }
            }

            public ConfluenceFactor Calculate(StrategyIntent intent, StrategyContext context, BarData bar)
            {
                return new ConfluenceFactor(
                    _type,
                    "Fixed",
                    _score,
                    _weight,
                    "Test factor",
                    new Dictionary<string, object>());
            }
        }
    }
}
