using System;
using System.Collections.Generic;
using NT8.Core.Common.Models;
using NT8.Core.Intelligence;
using NT8.Core.Logging;

namespace NT8.Core.Sizing
{
    /// <summary>
    /// Applies confluence grade and risk mode multipliers on top of base sizing output.
    /// </summary>
    public class GradeBasedSizer
    {
        private readonly ILogger _logger;
        private readonly GradeFilter _gradeFilter;

        /// <summary>
        /// Creates a grade-based sizer.
        /// </summary>
        /// <param name="logger">Logger instance.</param>
        /// <param name="gradeFilter">Grade filter instance.</param>
        public GradeBasedSizer(ILogger logger, GradeFilter gradeFilter)
        {
            if (logger == null)
                throw new ArgumentNullException("logger");
            if (gradeFilter == null)
                throw new ArgumentNullException("gradeFilter");

            _logger = logger;
            _gradeFilter = gradeFilter;
        }

        /// <summary>
        /// Calculates final size from base sizing plus grade and mode adjustments.
        /// </summary>
        /// <param name="intent">Strategy intent.</param>
        /// <param name="context">Strategy context.</param>
        /// <param name="confluenceScore">Confluence score with grade.</param>
        /// <param name="riskMode">Current risk mode.</param>
        /// <param name="baseConfig">Base sizing configuration.</param>
        /// <param name="baseSizer">Base position sizer used to compute initial contracts.</param>
        /// <param name="modeConfig">Current risk mode configuration.</param>
        /// <returns>Final sizing result including grade/mode metadata.</returns>
        public SizingResult CalculateGradeBasedSize(
            StrategyIntent intent,
            StrategyContext context,
            ConfluenceScore confluenceScore,
            RiskMode riskMode,
            SizingConfig baseConfig,
            IPositionSizer baseSizer,
            RiskModeConfig modeConfig)
        {
            if (intent == null)
                throw new ArgumentNullException("intent");
            if (context == null)
                throw new ArgumentNullException("context");
            if (confluenceScore == null)
                throw new ArgumentNullException("confluenceScore");
            if (baseConfig == null)
                throw new ArgumentNullException("baseConfig");
            if (baseSizer == null)
                throw new ArgumentNullException("baseSizer");
            if (modeConfig == null)
                throw new ArgumentNullException("modeConfig");

            try
            {
                if (!_gradeFilter.ShouldAcceptTrade(confluenceScore.Grade, riskMode))
                {
                    var reject = _gradeFilter.GetRejectionReason(confluenceScore.Grade, riskMode);
                    var rejectCalcs = new Dictionary<string, object>();
                    rejectCalcs.Add("rejected", true);
                    rejectCalcs.Add("rejection_reason", reject);
                    rejectCalcs.Add("grade", confluenceScore.Grade.ToString());
                    rejectCalcs.Add("risk_mode", riskMode.ToString());

                    _logger.LogInformation("Grade-based sizing rejected trade: {0}", reject);
                    return new SizingResult(0, 0.0, baseConfig.Method, rejectCalcs);
                }

                var baseResult = baseSizer.CalculateSize(intent, context, baseConfig);

                var gradeMultiplier = _gradeFilter.GetSizeMultiplier(confluenceScore.Grade, riskMode);
                var modeMultiplier = modeConfig.SizeMultiplier;
                var combinedMultiplier = CombineMultipliers(gradeMultiplier, modeMultiplier);

                var adjustedContractsRaw = baseResult.Contracts * combinedMultiplier;
                var adjustedContracts = ApplyConstraints(
                    (int)Math.Floor(adjustedContractsRaw),
                    baseConfig.MinContracts,
                    baseConfig.MaxContracts);

                var riskPerContract = baseResult.Contracts > 0 ? baseResult.RiskAmount / baseResult.Contracts : 0.0;
                var finalRisk = adjustedContracts * riskPerContract;

                var calculations = new Dictionary<string, object>();
                calculations.Add("base_contracts", baseResult.Contracts);
                calculations.Add("base_risk", baseResult.RiskAmount);
                calculations.Add("grade", confluenceScore.Grade.ToString());
                calculations.Add("risk_mode", riskMode.ToString());
                calculations.Add("grade_multiplier", gradeMultiplier);
                calculations.Add("mode_multiplier", modeMultiplier);
                calculations.Add("combined_multiplier", combinedMultiplier);
                calculations.Add("adjusted_contracts_raw", adjustedContractsRaw);
                calculations.Add("adjusted_contracts", adjustedContracts);
                calculations.Add("risk_per_contract", riskPerContract);
                calculations.Add("final_risk", finalRisk);

                return new SizingResult(adjustedContracts, finalRisk, baseResult.Method, calculations);
            }
            catch (Exception ex)
            {
                _logger.LogError("CalculateGradeBasedSize failed: {0}", ex.Message);
                throw;
            }
        }

        /// <summary>
        /// Combines grade and mode multipliers.
        /// </summary>
        /// <param name="gradeMultiplier">Grade-based multiplier.</param>
        /// <param name="modeMultiplier">Mode-based multiplier.</param>
        /// <returns>Combined multiplier.</returns>
        public double CombineMultipliers(double gradeMultiplier, double modeMultiplier)
        {
            if (gradeMultiplier < 0.0)
                throw new ArgumentException("gradeMultiplier must be non-negative", "gradeMultiplier");
            if (modeMultiplier < 0.0)
                throw new ArgumentException("modeMultiplier must be non-negative", "modeMultiplier");

            return gradeMultiplier * modeMultiplier;
        }

        /// <summary>
        /// Applies min/max contract constraints.
        /// </summary>
        /// <param name="calculatedSize">Calculated contracts.</param>
        /// <param name="min">Minimum allowed contracts.</param>
        /// <param name="max">Maximum allowed contracts.</param>
        /// <returns>Constrained contracts.</returns>
        public int ApplyConstraints(int calculatedSize, int min, int max)
        {
            if (min < 0)
                throw new ArgumentException("min must be non-negative", "min");
            if (max < min)
                throw new ArgumentException("max must be greater than or equal to min", "max");

            if (calculatedSize < min)
                return min;
            if (calculatedSize > max)
                return max;

            return calculatedSize;
        }
    }
}
