using System;
using System.Collections.Generic;
using Microsoft.Extensions.Logging;
using NT8.Core.MarketData;

namespace NT8.Core.Execution
{
    /// <summary>
    /// Handles contract roll operations for futures and other expiring instruments
    /// </summary>
    public class ContractRollHandler
    {
        private readonly ILogger _logger;
        private readonly object _lock = new object();

        // Store contract roll information
        private readonly Dictionary<string, ContractRollInfo> _rollInfo;

        // Store positions that need to be rolled
        private readonly Dictionary<string, OMS.OrderStatus> _positionsToRoll;

        /// <summary>
        /// Constructor for ContractRollHandler
        /// </summary>
        /// <param name="logger">Logger instance</param>
        public ContractRollHandler(ILogger<ContractRollHandler> logger)
        {
            if (logger == null)
                throw new ArgumentNullException("logger");

            _logger = logger;
            _rollInfo = new Dictionary<string, ContractRollInfo>();
            _positionsToRoll = new Dictionary<string, OMS.OrderStatus>();
        }

        /// <summary>
        /// Checks if it's currently in a contract roll period
        /// </summary>
        /// <param name="symbol">Base symbol to check (e.g., ES)</param>
        /// <param name="date">Date to check</param>
        /// <returns>True if in roll period, false otherwise</returns>
        public bool IsRollPeriod(string symbol, DateTime date)
        {
            if (string.IsNullOrEmpty(symbol))
                throw new ArgumentNullException("symbol");

            try
            {
                lock (_lock)
                {
                    if (_rollInfo.ContainsKey(symbol))
                    {
                        var rollInfo = _rollInfo[symbol];
                        var daysUntilRoll = (rollInfo.RollDate - date.Date).Days;

                        // Consider it rolling if within 5 days of roll date
                        return daysUntilRoll <= 5 && daysUntilRoll >= 0;
                    }

                    return false;
                }
            }
            catch (Exception ex)
            {
                _logger.LogError("Failed to check roll period for {Symbol}: {Message}", symbol, ex.Message);
                throw;
            }
        }

        /// <summary>
        /// Gets the active contract for a base symbol on a given date
        /// </summary>
        /// <param name="baseSymbol">Base symbol (e.g., ES)</param>
        /// <param name="date">Date to get contract for</param>
        /// <returns>Active contract symbol</returns>
        public string GetActiveContract(string baseSymbol, DateTime date)
        {
            if (string.IsNullOrEmpty(baseSymbol))
                throw new ArgumentNullException("baseSymbol");

            try
            {
                lock (_lock)
                {
                    if (_rollInfo.ContainsKey(baseSymbol))
                    {
                        var rollInfo = _rollInfo[baseSymbol];

                        // If we're past the roll date, return the next contract
                        if (date.Date >= rollInfo.RollDate)
                        {
                            return rollInfo.NextContract;
                        }
                        else
                        {
                            return rollInfo.ActiveContract;
                        }
                    }

                    // Default: just append date to base symbol (this would be configured externally in practice)
                    return baseSymbol + date.ToString("yyMM");
                }
            }
            catch (Exception ex)
            {
                _logger.LogError("Failed to get active contract for {Symbol}: {Message}", baseSymbol, ex.Message);
                throw;
            }
        }

        /// <summary>
        /// Determines if a position should be rolled
        /// </summary>
        /// <param name="symbol">Symbol of the position</param>
        /// <param name="position">Position details</param>
        /// <returns>Roll decision</returns>
        public RollDecision ShouldRollPosition(string symbol, OMS.OrderStatus position)
        {
            if (string.IsNullOrEmpty(symbol))
                throw new ArgumentNullException("symbol");
            if (position == null)
                throw new ArgumentNullException("position");

            try
            {
                lock (_lock)
                {
                    var baseSymbol = ExtractBaseSymbol(symbol);

                    if (_rollInfo.ContainsKey(baseSymbol))
                    {
                        var rollInfo = _rollInfo[baseSymbol];
                        var daysToRoll = rollInfo.DaysToRoll;

                        // Roll if we're within 3 days of roll date and position has quantity
                        if (daysToRoll <= 3 && position.RemainingQuantity > 0)
                        {
                            return new RollDecision(
                                true,
                                String.Format("Roll needed in {0} days", daysToRoll),
                                RollReason.ImminentExpiration
                            );
                        }
                        else if (daysToRoll <= 7 && position.RemainingQuantity > 0)
                        {
                            return new RollDecision(
                                true,
                                String.Format("Roll recommended in {0} days", daysToRoll),
                                RollReason.ApproachingExpiration
                            );
                        }
                    }

                    return new RollDecision(
                        false,
                        "No roll needed",
                        RollReason.None
                    );
                }
            }
            catch (Exception ex)
            {
                _logger.LogError("Failed to determine roll decision for {Symbol}: {Message}", symbol, ex.Message);
                throw;
            }
        }

        /// <summary>
        /// Initiates a contract rollover from one contract to another
        /// </summary>
        /// <param name="fromContract">Contract to roll from</param>
        /// <param name="toContract">Contract to roll to</param>
        public void InitiateRollover(string fromContract, string toContract)
        {
            if (string.IsNullOrEmpty(fromContract))
                throw new ArgumentNullException("fromContract");
            if (string.IsNullOrEmpty(toContract))
                throw new ArgumentNullException("toContract");

            try
            {
                lock (_lock)
                {
                    // Find positions in the from contract that need to be rolled
                    var positionsToClose = new List<OMS.OrderStatus>();
                    foreach (var kvp in _positionsToRoll)
                    {
                        if (kvp.Value.Symbol == fromContract && kvp.Value.State == OMS.OrderState.Working)
                        {
                            positionsToClose.Add(kvp.Value);
                        }
                    }

                    // Close positions in old contract
                    foreach (var position in positionsToClose)
                    {
                        // In a real implementation, this would submit close orders for the old contract
                        _logger.LogInformation("Initiating rollover: closing position in {FromContract}, size {Size}",
                            fromContract, position.RemainingQuantity);
                    }

                    // In a real implementation, this would establish new positions in the toContract
                    _logger.LogInformation("Rollover initiated from {FromContract} to {ToContract}",
                        fromContract, toContract);
                }
            }
            catch (Exception ex)
            {
                _logger.LogError("Failed to initiate rollover from {FromContract} to {ToContract}: {Message}",
                    fromContract, toContract, ex.Message);
                throw;
            }
        }

        /// <summary>
        /// Sets contract roll information for a symbol
        /// </summary>
        /// <param name="baseSymbol">Base symbol (e.g., ES)</param>
        /// <param name="activeContract">Current active contract (e.g., ESZ24)</param>
        /// <param name="nextContract">Next contract to roll to (e.g., ESH25)</param>
        /// <param name="rollDate">Date of the roll</param>
        public void SetRollInfo(string baseSymbol, string activeContract, string nextContract, DateTime rollDate)
        {
            if (string.IsNullOrEmpty(baseSymbol))
                throw new ArgumentNullException("baseSymbol");
            if (string.IsNullOrEmpty(activeContract))
                throw new ArgumentNullException("activeContract");
            if (string.IsNullOrEmpty(nextContract))
                throw new ArgumentNullException("nextContract");

            try
            {
                lock (_lock)
                {
                    var daysToRoll = (rollDate.Date - DateTime.UtcNow.Date).Days;
                    var isRollPeriod = daysToRoll <= 5 && daysToRoll >= 0;

                    var rollInfo = new ContractRollInfo(
                        baseSymbol,
                        activeContract,
                        nextContract,
                        rollDate,
                        daysToRoll,
                        isRollPeriod
                    );

                    _rollInfo[baseSymbol] = rollInfo;

                    _logger.LogDebug("Set roll info for {Symbol}: {ActiveContract} -> {NextContract} on {RollDate}",
                        baseSymbol, activeContract, nextContract, rollDate);
                }
            }
            catch (Exception ex)
            {
                _logger.LogError("Failed to set roll info for {Symbol}: {Message}", baseSymbol, ex.Message);
                throw;
            }
        }

        /// <summary>
        /// Adds a position that should be monitored for rolling
        /// </summary>
        /// <param name="position">Position to monitor</param>
        public void MonitorPositionForRoll(OMS.OrderStatus position)
        {
            if (position == null)
                throw new ArgumentNullException("position");

            try
            {
                lock (_lock)
                {
                    var key = position.OrderId;
                    _positionsToRoll[key] = position;

                    _logger.LogDebug("Added position {OrderId} for roll monitoring", position.OrderId);
                }
            }
            catch (Exception ex)
            {
                _logger.LogError("Failed to monitor position for roll: {Message}", ex.Message);
                throw;
            }
        }

        /// <summary>
        /// Removes a position from roll monitoring
        /// </summary>
        /// <param name="orderId">Order ID of position to remove</param>
        public void RemovePositionFromRollMonitoring(string orderId)
        {
            if (string.IsNullOrEmpty(orderId))
                throw new ArgumentNullException("orderId");

            try
            {
                lock (_lock)
                {
                    _positionsToRoll.Remove(orderId);

                    _logger.LogDebug("Removed position {OrderId} from roll monitoring", orderId);
                }
            }
            catch (Exception ex)
            {
                _logger.LogError("Failed to remove position from roll monitoring: {Message}", ex.Message);
                throw;
            }
        }

        /// <summary>
        /// Gets the roll information for a symbol
        /// </summary>
        /// <param name="baseSymbol">Base symbol to get roll info for</param>
        /// <returns>Contract roll information</returns>
        public ContractRollInfo GetRollInfo(string baseSymbol)
        {
            if (string.IsNullOrEmpty(baseSymbol))
                throw new ArgumentNullException("baseSymbol");

            try
            {
                lock (_lock)
                {
                    ContractRollInfo info;
                    _rollInfo.TryGetValue(baseSymbol, out info);
                    return info;
                }
            }
            catch (Exception ex)
            {
                _logger.LogError("Failed to get roll info for {Symbol}: {Message}", baseSymbol, ex.Message);
                throw;
            }
        }

        /// <summary>
        /// Extracts the base symbol from a contract symbol
        /// </summary>
        /// <param name="contractSymbol">Full contract symbol (e.g., ESZ24)</param>
        /// <returns>Base symbol (e.g., ES)</returns>
        private string ExtractBaseSymbol(string contractSymbol)
        {
            if (string.IsNullOrEmpty(contractSymbol))
                return string.Empty;

            // For now, extract letters from the beginning
            // In practice, this would be more sophisticated
            var baseSymbol = "";
            foreach (char c in contractSymbol)
            {
                if (char.IsLetter(c))
                    baseSymbol += c;
                else
                    break;
            }

            return baseSymbol;
        }
    }

    /// <summary>
    /// Decision regarding contract rolling
    /// </summary>
    public class RollDecision
    {
        /// <summary>
        /// Whether the position should be rolled
        /// </summary>
        public bool ShouldRoll { get; set; }

        /// <summary>
        /// Reason for the decision
        /// </summary>
        public string Reason { get; set; }

        /// <summary>
        /// Reason category
        /// </summary>
        public RollReason RollReason { get; set; }

        /// <summary>
        /// Constructor for RollDecision
        /// </summary>
        /// <param name="shouldRoll">Whether to roll</param>
        /// <param name="reason">Reason for decision</param>
        /// <param name="rollReason">Category of reason</param>
        public RollDecision(bool shouldRoll, string reason, RollReason rollReason)
        {
            ShouldRoll = shouldRoll;
            Reason = reason;
            RollReason = rollReason;
        }
    }

    /// <summary>
    /// Reason for contract roll
    /// </summary>
    public enum RollReason
    {
        /// <summary>
        /// No roll needed
        /// </summary>
        None = 0,

        /// <summary>
        /// Approaching expiration
        /// </summary>
        ApproachingExpiration = 1,

        /// <summary>
        /// Imminent expiration
        /// </summary>
        ImminentExpiration = 2,

        /// <summary>
        /// Better liquidity in next contract
        /// </summary>
        BetterLiquidity = 3,

        /// <summary>
        /// Scheduled roll
        /// </summary>
        Scheduled = 4
    }
}
