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

namespace NT8.Core.Analytics
{
    /// <summary>
    /// Records and queries complete trade lifecycle information.
    /// </summary>
    public class TradeRecorder
    {
        private readonly ILogger _logger;
        private readonly object _lock;
        private readonly Dictionary<string, TradeRecord> _trades;
        private readonly Dictionary<string, List<OrderFill>> _fillsByTrade;

        /// <summary>
        /// Initializes a new instance of the trade recorder.
        /// </summary>
        /// <param name="logger">Logger implementation.</param>
        public TradeRecorder(ILogger logger)
        {
            if (logger == null)
                throw new ArgumentNullException("logger");

            _logger = logger;
            _lock = new object();
            _trades = new Dictionary<string, TradeRecord>();
            _fillsByTrade = new Dictionary<string, List<OrderFill>>();
        }

        /// <summary>
        /// Records trade entry details.
        /// </summary>
        /// <param name="tradeId">Trade identifier.</param>
        /// <param name="intent">Strategy intent used for the trade.</param>
        /// <param name="fill">Entry fill event.</param>
        /// <param name="score">Confluence score at entry.</param>
        /// <param name="mode">Risk mode at entry.</param>
        public void RecordEntry(string tradeId, StrategyIntent intent, OrderFill fill, ConfluenceScore score, RiskMode mode)
        {
            if (string.IsNullOrEmpty(tradeId))
                throw new ArgumentNullException("tradeId");
            if (intent == null)
                throw new ArgumentNullException("intent");
            if (fill == null)
                throw new ArgumentNullException("fill");
            if (score == null)
                throw new ArgumentNullException("score");

            try
            {
                var record = new TradeRecord();
                record.TradeId = tradeId;
                record.Symbol = intent.Symbol;
                record.StrategyName = ResolveStrategyName(intent);
                record.EntryTime = fill.FillTime;
                record.ExitTime = null;
                record.Side = intent.Side;
                record.Quantity = fill.Quantity;
                record.EntryPrice = fill.FillPrice;
                record.ExitPrice = null;
                record.RealizedPnL = 0.0;
                record.UnrealizedPnL = 0.0;
                record.Grade = score.Grade;
                record.ConfluenceScore = score.WeightedScore;
                record.RiskMode = mode;
                record.VolatilityRegime = ResolveVolatilityRegime(intent, score);
                record.TrendRegime = ResolveTrendRegime(intent, score);
                record.StopTicks = intent.StopTicks;
                record.TargetTicks = intent.TargetTicks.HasValue ? intent.TargetTicks.Value : 0;
                record.RMultiple = 0.0;
                record.Duration = TimeSpan.Zero;
                record.Metadata.Add("entry_fill_id", fill.ExecutionId ?? string.Empty);
                record.Metadata.Add("entry_commission", fill.Commission);

                lock (_lock)
                {
                    _trades[tradeId] = record;
                    if (!_fillsByTrade.ContainsKey(tradeId))
                        _fillsByTrade.Add(tradeId, new List<OrderFill>());
                    _fillsByTrade[tradeId].Add(fill);
                }

                _logger.LogInformation("Trade entry recorded: {0} {1} {2} @ {3:F2}",
                    tradeId, record.Symbol, record.Quantity, record.EntryPrice);
            }
            catch (Exception ex)
            {
                _logger.LogError("RecordEntry failed for trade {0}: {1}", tradeId, ex.Message);
                throw;
            }
        }

        /// <summary>
        /// Records full trade exit and finalizes metrics.
        /// </summary>
        /// <param name="tradeId">Trade identifier.</param>
        /// <param name="fill">Exit fill event.</param>
        public void RecordExit(string tradeId, OrderFill fill)
        {
            if (string.IsNullOrEmpty(tradeId))
                throw new ArgumentNullException("tradeId");
            if (fill == null)
                throw new ArgumentNullException("fill");

            try
            {
                lock (_lock)
                {
                    if (!_trades.ContainsKey(tradeId))
                        throw new ArgumentException("Trade not found", "tradeId");

                    var record = _trades[tradeId];
                    record.ExitTime = fill.FillTime;
                    record.ExitPrice = fill.FillPrice;
                    record.Duration = record.ExitTime.Value - record.EntryTime;

                    if (!_fillsByTrade.ContainsKey(tradeId))
                        _fillsByTrade.Add(tradeId, new List<OrderFill>());
                    _fillsByTrade[tradeId].Add(fill);

                    var totalExitQty = _fillsByTrade[tradeId]
                        .Skip(1)
                        .Sum(f => f.Quantity);
                    if (totalExitQty > 0)
                    {
                        var weightedExitPrice = _fillsByTrade[tradeId]
                            .Skip(1)
                            .Sum(f => f.FillPrice * f.Quantity) / totalExitQty;
                        record.ExitPrice = weightedExitPrice;
                    }

                    var signedMove = (record.ExitPrice.HasValue ? record.ExitPrice.Value : record.EntryPrice) - record.EntryPrice;
                    if (record.Side == OrderSide.Sell)
                        signedMove = -signedMove;

                    record.RealizedPnL = signedMove * record.Quantity;
                    record.UnrealizedPnL = 0.0;

                    var stopRisk = record.StopTicks <= 0 ? 0.0 : record.StopTicks;
                    if (stopRisk > 0.0)
                        record.RMultiple = signedMove / stopRisk;

                    record.Metadata["exit_fill_id"] = fill.ExecutionId ?? string.Empty;
                    record.Metadata["exit_commission"] = fill.Commission;
                }

                _logger.LogInformation("Trade exit recorded: {0}", tradeId);
            }
            catch (Exception ex)
            {
                _logger.LogError("RecordExit failed for trade {0}: {1}", tradeId, ex.Message);
                throw;
            }
        }

        /// <summary>
        /// Records a partial fill event.
        /// </summary>
        /// <param name="tradeId">Trade identifier.</param>
        /// <param name="fill">Partial fill event.</param>
        public void RecordPartialFill(string tradeId, OrderFill fill)
        {
            if (string.IsNullOrEmpty(tradeId))
                throw new ArgumentNullException("tradeId");
            if (fill == null)
                throw new ArgumentNullException("fill");

            try
            {
                lock (_lock)
                {
                    if (!_fillsByTrade.ContainsKey(tradeId))
                        _fillsByTrade.Add(tradeId, new List<OrderFill>());
                    _fillsByTrade[tradeId].Add(fill);

                    if (_trades.ContainsKey(tradeId))
                    {
                        _trades[tradeId].Metadata["partial_fill_count"] = _fillsByTrade[tradeId].Count;
                    }
                }
            }
            catch (Exception ex)
            {
                _logger.LogError("RecordPartialFill failed for trade {0}: {1}", tradeId, ex.Message);
                throw;
            }
        }

        /// <summary>
        /// Gets a single trade by identifier.
        /// </summary>
        /// <param name="tradeId">Trade identifier.</param>
        /// <returns>Trade record if found.</returns>
        public TradeRecord GetTrade(string tradeId)
        {
            if (string.IsNullOrEmpty(tradeId))
                throw new ArgumentNullException("tradeId");

            try
            {
                lock (_lock)
                {
                    TradeRecord record;
                    if (!_trades.TryGetValue(tradeId, out record))
                        return null;
                    return Clone(record);
                }
            }
            catch (Exception ex)
            {
                _logger.LogError("GetTrade failed for trade {0}: {1}", tradeId, ex.Message);
                throw;
            }
        }

        /// <summary>
        /// Gets trades in a time range.
        /// </summary>
        /// <param name="start">Start timestamp inclusive.</param>
        /// <param name="end">End timestamp inclusive.</param>
        /// <returns>Trade records in range.</returns>
        public List<TradeRecord> GetTrades(DateTime start, DateTime end)
        {
            try
            {
                lock (_lock)
                {
                    return _trades.Values
                        .Where(t => t.EntryTime >= start && t.EntryTime <= end)
                        .OrderBy(t => t.EntryTime)
                        .Select(Clone)
                        .ToList();
                }
            }
            catch (Exception ex)
            {
                _logger.LogError("GetTrades failed: {0}", ex.Message);
                throw;
            }
        }

        /// <summary>
        /// Gets trades by grade.
        /// </summary>
        /// <param name="grade">Target grade.</param>
        /// <returns>Trade list.</returns>
        public List<TradeRecord> GetTradesByGrade(TradeGrade grade)
        {
            try
            {
                lock (_lock)
                {
                    return _trades.Values
                        .Where(t => t.Grade == grade)
                        .OrderBy(t => t.EntryTime)
                        .Select(Clone)
                        .ToList();
                }
            }
            catch (Exception ex)
            {
                _logger.LogError("GetTradesByGrade failed: {0}", ex.Message);
                throw;
            }
        }

        /// <summary>
        /// Gets trades by strategy name.
        /// </summary>
        /// <param name="strategyName">Strategy name.</param>
        /// <returns>Trade list.</returns>
        public List<TradeRecord> GetTradesByStrategy(string strategyName)
        {
            if (string.IsNullOrEmpty(strategyName))
                throw new ArgumentNullException("strategyName");

            try
            {
                lock (_lock)
                {
                    return _trades.Values
                        .Where(t => string.Equals(t.StrategyName, strategyName, StringComparison.OrdinalIgnoreCase))
                        .OrderBy(t => t.EntryTime)
                        .Select(Clone)
                        .ToList();
                }
            }
            catch (Exception ex)
            {
                _logger.LogError("GetTradesByStrategy failed: {0}", ex.Message);
                throw;
            }
        }

        /// <summary>
        /// Exports all trades to CSV.
        /// </summary>
        /// <returns>CSV text.</returns>
        public string ExportToCsv()
        {
            try
            {
                var rows = new StringBuilder();
                rows.AppendLine("TradeId,Symbol,StrategyName,EntryTime,ExitTime,Side,Quantity,EntryPrice,ExitPrice,RealizedPnL,Grade,RiskMode,VolatilityRegime,TrendRegime,RMultiple");

                List<TradeRecord> trades;
                lock (_lock)
                {
                    trades = _trades.Values.OrderBy(t => t.EntryTime).Select(Clone).ToList();
                }

                foreach (var trade in trades)
                {
                    rows.AppendFormat(CultureInfo.InvariantCulture,
                        "{0},{1},{2},{3:O},{4},{5},{6},{7:F4},{8},{9:F2},{10},{11},{12},{13},{14:F4}",
                        EscapeCsv(trade.TradeId),
                        EscapeCsv(trade.Symbol),
                        EscapeCsv(trade.StrategyName),
                        trade.EntryTime,
                        trade.ExitTime.HasValue ? trade.ExitTime.Value.ToString("O") : string.Empty,
                        trade.Side,
                        trade.Quantity,
                        trade.EntryPrice,
                        trade.ExitPrice.HasValue ? trade.ExitPrice.Value.ToString("F4", CultureInfo.InvariantCulture) : string.Empty,
                        trade.RealizedPnL,
                        trade.Grade,
                        trade.RiskMode,
                        trade.VolatilityRegime,
                        trade.TrendRegime,
                        trade.RMultiple);
                    rows.AppendLine();
                }

                return rows.ToString();
            }
            catch (Exception ex)
            {
                _logger.LogError("ExportToCsv failed: {0}", ex.Message);
                throw;
            }
        }

        /// <summary>
        /// Exports all trades to JSON.
        /// </summary>
        /// <returns>JSON text.</returns>
        public string ExportToJson()
        {
            try
            {
                List<TradeRecord> trades;
                lock (_lock)
                {
                    trades = _trades.Values.OrderBy(t => t.EntryTime).Select(Clone).ToList();
                }

                var builder = new StringBuilder();
                builder.Append("[");

                for (var i = 0; i < trades.Count; i++)
                {
                    var trade = trades[i];
                    if (i > 0)
                        builder.Append(",");

                    builder.Append("{");
                    builder.AppendFormat(CultureInfo.InvariantCulture, "\"tradeId\":\"{0}\"", EscapeJson(trade.TradeId));
                    builder.AppendFormat(CultureInfo.InvariantCulture, ",\"symbol\":\"{0}\"", EscapeJson(trade.Symbol));
                    builder.AppendFormat(CultureInfo.InvariantCulture, ",\"strategyName\":\"{0}\"", EscapeJson(trade.StrategyName));
                    builder.AppendFormat(CultureInfo.InvariantCulture, ",\"entryTime\":\"{0:O}\"", trade.EntryTime);
                    if (trade.ExitTime.HasValue)
                        builder.AppendFormat(CultureInfo.InvariantCulture, ",\"exitTime\":\"{0:O}\"", trade.ExitTime.Value);
                    else
                        builder.Append(",\"exitTime\":null");
                    builder.AppendFormat(CultureInfo.InvariantCulture, ",\"side\":\"{0}\"", trade.Side);
                    builder.AppendFormat(CultureInfo.InvariantCulture, ",\"quantity\":{0}", trade.Quantity);
                    builder.AppendFormat(CultureInfo.InvariantCulture, ",\"entryPrice\":{0}", trade.EntryPrice);
                    if (trade.ExitPrice.HasValue)
                        builder.AppendFormat(CultureInfo.InvariantCulture, ",\"exitPrice\":{0}", trade.ExitPrice.Value);
                    else
                        builder.Append(",\"exitPrice\":null");
                    builder.AppendFormat(CultureInfo.InvariantCulture, ",\"realizedPnL\":{0}", trade.RealizedPnL);
                    builder.AppendFormat(CultureInfo.InvariantCulture, ",\"grade\":\"{0}\"", trade.Grade);
                    builder.AppendFormat(CultureInfo.InvariantCulture, ",\"riskMode\":\"{0}\"", trade.RiskMode);
                    builder.Append("}");
                }

                builder.Append("]");
                return builder.ToString();
            }
            catch (Exception ex)
            {
                _logger.LogError("ExportToJson failed: {0}", ex.Message);
                throw;
            }
        }

        private static string ResolveStrategyName(StrategyIntent intent)
        {
            object name;
            if (intent.Metadata != null && intent.Metadata.TryGetValue("strategy_name", out name) && name != null)
                return name.ToString();
            return "Unknown";
        }

        private static VolatilityRegime ResolveVolatilityRegime(StrategyIntent intent, ConfluenceScore score)
        {
            object value;
            if (TryGetMetadataValue(intent, score, "volatility_regime", out value))
            {
                VolatilityRegime parsed;
                if (Enum.TryParse(value.ToString(), true, out parsed))
                    return parsed;
            }
            return VolatilityRegime.Normal;
        }

        private static TrendRegime ResolveTrendRegime(StrategyIntent intent, ConfluenceScore score)
        {
            object value;
            if (TryGetMetadataValue(intent, score, "trend_regime", out value))
            {
                TrendRegime parsed;
                if (Enum.TryParse(value.ToString(), true, out parsed))
                    return parsed;
            }
            return TrendRegime.Range;
        }

        private static bool TryGetMetadataValue(StrategyIntent intent, ConfluenceScore score, string key, out object value)
        {
            value = null;
            if (intent.Metadata != null && intent.Metadata.TryGetValue(key, out value))
                return true;
            if (score.Metadata != null && score.Metadata.TryGetValue(key, out value))
                return true;
            return false;
        }

        private static TradeRecord Clone(TradeRecord input)
        {
            var clone = new TradeRecord();
            clone.TradeId = input.TradeId;
            clone.Symbol = input.Symbol;
            clone.StrategyName = input.StrategyName;
            clone.EntryTime = input.EntryTime;
            clone.ExitTime = input.ExitTime;
            clone.Side = input.Side;
            clone.Quantity = input.Quantity;
            clone.EntryPrice = input.EntryPrice;
            clone.ExitPrice = input.ExitPrice;
            clone.RealizedPnL = input.RealizedPnL;
            clone.UnrealizedPnL = input.UnrealizedPnL;
            clone.Grade = input.Grade;
            clone.ConfluenceScore = input.ConfluenceScore;
            clone.RiskMode = input.RiskMode;
            clone.VolatilityRegime = input.VolatilityRegime;
            clone.TrendRegime = input.TrendRegime;
            clone.StopTicks = input.StopTicks;
            clone.TargetTicks = input.TargetTicks;
            clone.RMultiple = input.RMultiple;
            clone.Duration = input.Duration;
            clone.Metadata = new Dictionary<string, object>(input.Metadata);
            return clone;
        }

        private static string EscapeCsv(string value)
        {
            if (value == null)
                return string.Empty;

            if (value.Contains(",") || value.Contains("\"") || value.Contains("\n") || value.Contains("\r"))
                return string.Format("\"{0}\"", value.Replace("\"", "\"\""));

            return value;
        }

        private static string EscapeJson(string value)
        {
            if (value == null)
                return string.Empty;

            return value
                .Replace("\\", "\\\\")
                .Replace("\"", "\\\"")
                .Replace("\r", "\\r")
                .Replace("\n", "\\n");
        }
    }
}
