using System;
using System.Collections.Generic;
using System.Linq;
using NT8.Core.Intelligence;
using NT8.Core.Logging;

namespace NT8.Core.Analytics
{
    /// <summary>
    /// Provides PnL attribution analysis across multiple dimensions.
    /// </summary>
    public class PnLAttributor
    {
        private readonly ILogger _logger;

        /// <summary>
        /// Initializes a new attributor instance.
        /// </summary>
        /// <param name="logger">Logger dependency.</param>
        public PnLAttributor(ILogger logger)
        {
            if (logger == null)
                throw new ArgumentNullException("logger");

            _logger = logger;
        }

        /// <summary>
        /// Attributes PnL by trade grade.
        /// </summary>
        /// <param name="trades">Trade records.</param>
        /// <returns>Attribution report.</returns>
        public AttributionReport AttributeByGrade(List<TradeRecord> trades)
        {
            return BuildReport(trades, AttributionDimension.Grade, t => t.Grade.ToString());
        }

        /// <summary>
        /// Attributes PnL by combined volatility and trend regime.
        /// </summary>
        /// <param name="trades">Trade records.</param>
        /// <returns>Attribution report.</returns>
        public AttributionReport AttributeByRegime(List<TradeRecord> trades)
        {
            return BuildReport(
                trades,
                AttributionDimension.Regime,
                t => string.Format("{0}|{1}", t.VolatilityRegime, t.TrendRegime));
        }

        /// <summary>
        /// Attributes PnL by strategy name.
        /// </summary>
        /// <param name="trades">Trade records.</param>
        /// <returns>Attribution report.</returns>
        public AttributionReport AttributeByStrategy(List<TradeRecord> trades)
        {
            return BuildReport(trades, AttributionDimension.Strategy, t => t.StrategyName ?? string.Empty);
        }

        /// <summary>
        /// Attributes PnL by time-of-day bucket.
        /// </summary>
        /// <param name="trades">Trade records.</param>
        /// <returns>Attribution report.</returns>
        public AttributionReport AttributeByTimeOfDay(List<TradeRecord> trades)
        {
            return BuildReport(trades, AttributionDimension.Time, GetTimeBucket);
        }

        /// <summary>
        /// Attributes PnL by a multi-dimensional combined key.
        /// </summary>
        /// <param name="trades">Trade records.</param>
        /// <param name="dimensions">Dimensions to combine.</param>
        /// <returns>Attribution report.</returns>
        public AttributionReport AttributeMultiDimensional(List<TradeRecord> trades, List<AttributionDimension> dimensions)
        {
            if (dimensions == null)
                throw new ArgumentNullException("dimensions");
            if (dimensions.Count == 0)
                throw new ArgumentException("At least one dimension is required", "dimensions");

            try
            {
                return BuildReport(trades, AttributionDimension.Strategy, delegate(TradeRecord trade)
                {
                    var parts = new List<string>();
                    foreach (var dimension in dimensions)
                    {
                        parts.Add(GetDimensionValue(trade, dimension));
                    }

                    return string.Join("|", parts.ToArray());
                });
            }
            catch (Exception ex)
            {
                _logger.LogError("AttributeMultiDimensional failed: {0}", ex.Message);
                throw;
            }
        }

        private AttributionReport BuildReport(
            List<TradeRecord> trades,
            AttributionDimension dimension,
            Func<TradeRecord, string> keySelector)
        {
            if (trades == null)
                throw new ArgumentNullException("trades");
            if (keySelector == null)
                throw new ArgumentNullException("keySelector");

            try
            {
                var report = new AttributionReport();
                report.Dimension = dimension;
                report.TotalTrades = trades.Count;
                report.TotalPnL = trades.Sum(t => t.RealizedPnL);

                var groups = trades.GroupBy(keySelector).ToList();
                foreach (var group in groups)
                {
                    var tradeList = group.ToList();
                    var totalPnL = tradeList.Sum(t => t.RealizedPnL);
                    var wins = tradeList.Count(t => t.RealizedPnL > 0.0);
                    var losses = tradeList.Count(t => t.RealizedPnL < 0.0);
                    var grossProfit = tradeList.Where(t => t.RealizedPnL > 0.0).Sum(t => t.RealizedPnL);
                    var grossLoss = Math.Abs(tradeList.Where(t => t.RealizedPnL < 0.0).Sum(t => t.RealizedPnL));

                    var slice = new AttributionSlice();
                    slice.DimensionName = dimension.ToString();
                    slice.DimensionValue = group.Key;
                    slice.TotalPnL = totalPnL;
                    slice.TradeCount = tradeList.Count;
                    slice.AvgPnL = tradeList.Count > 0 ? totalPnL / tradeList.Count : 0.0;
                    slice.WinRate = tradeList.Count > 0 ? (double)wins / tradeList.Count : 0.0;
                    slice.ProfitFactor = grossLoss > 0.0
                        ? grossProfit / grossLoss
                        : (grossProfit > 0.0 ? double.PositiveInfinity : 0.0);
                    slice.Contribution = report.TotalPnL != 0.0 ? totalPnL / report.TotalPnL : 0.0;

                    report.Slices.Add(slice);
                }

                report.Slices = report.Slices
                    .OrderByDescending(s => s.TotalPnL)
                    .ToList();

                report.Metadata.Add("group_count", report.Slices.Count);
                report.Metadata.Add("winners", trades.Count(t => t.RealizedPnL > 0.0));
                report.Metadata.Add("losers", trades.Count(t => t.RealizedPnL < 0.0));

                return report;
            }
            catch (Exception ex)
            {
                _logger.LogError("BuildReport failed for dimension {0}: {1}", dimension, ex.Message);
                throw;
            }
        }

        private static string GetTimeBucket(TradeRecord trade)
        {
            var local = trade.EntryTime;
            var time = local.TimeOfDay;

            if (time < new TimeSpan(10, 30, 0))
                return "FirstHour";
            if (time < new TimeSpan(14, 0, 0))
                return "MidDay";
            if (time < new TimeSpan(16, 0, 0))
                return "LastHour";

            return "AfterHours";
        }

        private static string GetDimensionValue(TradeRecord trade, AttributionDimension dimension)
        {
            switch (dimension)
            {
                case AttributionDimension.Strategy:
                    return trade.StrategyName ?? string.Empty;
                case AttributionDimension.Grade:
                    return trade.Grade.ToString();
                case AttributionDimension.Regime:
                    return string.Format("{0}|{1}", trade.VolatilityRegime, trade.TrendRegime);
                case AttributionDimension.Time:
                    return GetTimeBucket(trade);
                case AttributionDimension.Symbol:
                    return trade.Symbol ?? string.Empty;
                case AttributionDimension.RiskMode:
                    return trade.RiskMode.ToString();
                default:
                    return string.Empty;
            }
        }
    }
}
