using System;
using System.Collections.Generic;
using Microsoft.Extensions.Logging;

namespace NT8.Core.MarketData
{
    /// <summary>
    /// Manages trading session information for different symbols
    /// </summary>
    public class SessionManager
    {
        private readonly ILogger _logger;
        private readonly object _lock = new object();

        // Store session information for each symbol
        private readonly Dictionary<string, SessionInfo> _sessionCache;
        private readonly Dictionary<string, ContractRollInfo> _contractRollCache;

        // CME US Futures holidays — markets closed all day on these dates.
        // Update annually. Dates are in Eastern Time calendar dates.
        private static readonly HashSet<DateTime> _cmeHolidays = new HashSet<DateTime>
        {
            // 2025 holidays
            new DateTime(2025, 1, 1),
            new DateTime(2025, 1, 20),
            new DateTime(2025, 2, 17),
            new DateTime(2025, 4, 18),
            new DateTime(2025, 5, 26),
            new DateTime(2025, 6, 19),
            new DateTime(2025, 7, 4),
            new DateTime(2025, 9, 1),
            new DateTime(2025, 11, 27),
            new DateTime(2025, 12, 25),

            // 2026 holidays
            new DateTime(2026, 1, 1),
            new DateTime(2026, 1, 19),
            new DateTime(2026, 2, 16),
            new DateTime(2026, 4, 3),
            new DateTime(2026, 5, 25),
            new DateTime(2026, 6, 19),
            new DateTime(2026, 7, 4),
            new DateTime(2026, 9, 7),
            new DateTime(2026, 11, 26),
            new DateTime(2026, 12, 25)
        };

        // Helper class to store session times
        private class SessionTimes
        {
            public TimeSpan Start { get; set; }
            public TimeSpan End { get; set; }

            public SessionTimes(TimeSpan start, TimeSpan end)
            {
                Start = start;
                End = end;
            }
        }

        // Default session times (EST timezone for US futures)
        private readonly Dictionary<string, SessionTimes> _defaultSessions;

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

            _logger = logger;
            _sessionCache = new Dictionary<string, SessionInfo>();
            _contractRollCache = new Dictionary<string, ContractRollInfo>();

            // Initialize default session times for common futures symbols
            _defaultSessions = new Dictionary<string, SessionTimes>
            {
                // E-mini S&P 500 (ES)
                { "ES", new SessionTimes(new TimeSpan(8, 30, 0), new TimeSpan(15, 15, 0)) },

                // E-mini Nasdaq 100 (NQ)
                { "NQ", new SessionTimes(new TimeSpan(8, 30, 0), new TimeSpan(15, 15, 0)) },

                // E-mini Dow Jones (YM)
                { "YM", new SessionTimes(new TimeSpan(8, 30, 0), new TimeSpan(15, 15, 0)) },

                // Crude Oil (CL)
                { "CL", new SessionTimes(new TimeSpan(9, 0, 0), new TimeSpan(14, 30, 0)) },

                // Gold (GC)
                { "GC", new SessionTimes(new TimeSpan(17, 0, 0), new TimeSpan(16, 0, 0)) }, // Overnight session

                // Treasury Bonds (ZN)
                { "ZN", new SessionTimes(new TimeSpan(8, 20, 0), new TimeSpan(15, 0, 0)) }
            };
        }

        /// <summary>
        /// Gets the current trading session for a symbol
        /// </summary>
        /// <param name="symbol">Symbol to get session for</param>
        /// <param name="time">Time to check session for</param>
        /// <returns>Session information for the symbol</returns>
        public SessionInfo GetCurrentSession(string symbol, DateTime time)
        {
            if (string.IsNullOrEmpty(symbol))
                throw new ArgumentNullException("symbol");

            try
            {
                lock (_lock)
                {
                    // Convert time to EST for comparison (assuming US futures)
                    var estTime = TimeZoneInfo.ConvertTimeFromUtc(time,
                        TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time"));

                    var currentTime = estTime.TimeOfDay;

                    // Check if we have specific session info for this symbol
                    if (_sessionCache.ContainsKey(symbol))
                    {
                        var cached = _sessionCache[symbol];
                        if (cached.SessionStart.Date == estTime.Date)
                        {
                            return cached;
                        }
                    }

                    // Determine session based on default times
                    var sessionType = TradingSession.Closed;
                    var isRegularHours = false;
                    TimeSpan sessionStart = TimeSpan.Zero;
                    TimeSpan sessionEnd = TimeSpan.Zero;

                    if (_defaultSessions.ContainsKey(symbol))
                    {
                        var sessionTimes = _defaultSessions[symbol];
                        var start = sessionTimes.Start;
                        var end = sessionTimes.End;

                        // Handle overnight sessions (end time before start time)
                        if (sessionTimes.End < sessionTimes.Start)
                        {
                            // Overnight session (e.g., GC)
                            if (currentTime >= sessionTimes.Start || currentTime < sessionTimes.End)
                            {
                                sessionType = TradingSession.RTH;
                                isRegularHours = true;
                                sessionStart = sessionTimes.Start;

                                // If current time is before end, session continues to next day
                                if (currentTime < sessionTimes.End)
                                {
                                    sessionEnd = sessionTimes.End;
                                }
                                else
                                {
                                    // Session continues until next day's end time
                                    sessionEnd = sessionTimes.End.Add(TimeSpan.FromDays(1));
                                }
                            }
                            else
                            {
                                // Check if we're in the overnight session that started yesterday
                                if (currentTime < sessionTimes.End)
                                {
                                    // Check if we're continuing from yesterday's session
                                    var yesterday = estTime.AddDays(-1);
                                    if (currentTime >= sessionTimes.Start || currentTime < sessionTimes.End)
                                    {
                                        sessionType = TradingSession.RTH;
                                        isRegularHours = true;
                                        sessionStart = sessionTimes.Start;
                                        sessionEnd = sessionTimes.End.Add(TimeSpan.FromDays(1));
                                    }
                                }
                            }
                        }
                        else
                        {
                            // Regular session (start to end same day)
                            if (currentTime >= sessionTimes.Start && currentTime < sessionTimes.End)
                            {
                                sessionType = TradingSession.RTH;
                                isRegularHours = true;
                                sessionStart = sessionTimes.Start;
                                sessionEnd = sessionTimes.End;
                            }
                            else if (currentTime < sessionTimes.Start)
                            {
                                sessionType = TradingSession.PreMarket;
                                sessionStart = sessionTimes.Start;
                                sessionEnd = sessionTimes.End;
                            }
                            else if (currentTime >= sessionTimes.End)
                            {
                                sessionType = TradingSession.ETH;
                                sessionStart = sessionTimes.Start;
                                sessionEnd = sessionTimes.End;
                            }
                        }
                    }
                    else
                    {
                        // Default to closed if no specific session info
                        sessionType = TradingSession.Closed;
                    }

                    // Calculate time remaining in session
                    TimeSpan timeRemaining = TimeSpan.Zero;
                    if (sessionType == TradingSession.RTH)
                    {
                        timeRemaining = sessionEnd > currentTime ? sessionEnd - currentTime : TimeSpan.Zero;
                    }

                    var sessionInfo = new SessionInfo(
                        symbol,
                        sessionType,
                        new DateTime(estTime.Year, estTime.Month, estTime.Day) + sessionStart,
                        new DateTime(estTime.Year, estTime.Month, estTime.Day) + sessionEnd,
                        timeRemaining,
                        isRegularHours
                    );

                    // Cache the session info
                    _sessionCache[symbol] = sessionInfo;

                    _logger.LogDebug("Session for {Symbol} at {Time}: {SessionType} (RTH: {IsRTH})",
                        symbol, time, sessionType, isRegularHours);

                    return sessionInfo;
                }
            }
            catch (Exception ex)
            {
                _logger.LogError("Failed to get current session for {Symbol}: {Message}", symbol, ex.Message);
                throw;
            }
        }

        /// <summary>
        /// Checks if it's regular trading hours for a symbol
        /// </summary>
        /// <param name="symbol">Symbol to check</param>
        /// <param name="time">Time to check</param>
        /// <returns>True if it's regular trading hours, false otherwise</returns>
        public bool IsRegularTradingHours(string symbol, DateTime time)
        {
            if (string.IsNullOrEmpty(symbol))
                throw new ArgumentNullException("symbol");

            try
            {
                // Markets are fully closed on CME holidays
                if (IsCmeHoliday(time))
                {
                    _logger.LogInformation("Holiday detected for {Symbol} on {Date} - market closed.", symbol, time.Date);
                    return false;
                }

                var sessionInfo = GetCurrentSession(symbol, time);
                return sessionInfo.IsRegularHours;
            }
            catch (Exception ex)
            {
                _logger.LogError("Failed to check RTH for {Symbol}: {Message}", symbol, ex.Message);
                throw;
            }
        }

        /// <summary>
        /// Returns true if the given UTC date is a CME holiday (market closed all day).
        /// </summary>
        /// <param name="utcTime">UTC timestamp to evaluate</param>
        /// <returns>True if the Eastern date is a known CME holiday, false otherwise</returns>
        private static bool IsCmeHoliday(DateTime utcTime)
        {
            try
            {
                var eastern = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
                var estTime = TimeZoneInfo.ConvertTimeFromUtc(utcTime, eastern);
                return _cmeHolidays.Contains(estTime.Date);
            }
            catch (Exception)
            {
                return false;
            }
        }

        /// <summary>
        /// Checks if a contract is in its roll period
        /// </summary>
        /// <param name="symbol">Symbol to check</param>
        /// <param name="time">Time to check</param>
        /// <returns>True if in roll period, false otherwise</returns>
        public bool IsContractRolling(string symbol, DateTime time)
        {
            if (string.IsNullOrEmpty(symbol))
                throw new ArgumentNullException("symbol");

            try
            {
                lock (_lock)
                {
                    // Check if we have cached roll info for this symbol
                    if (_contractRollCache.ContainsKey(symbol))
                    {
                        var rollInfo = _contractRollCache[symbol];
                        var daysUntilRoll = (rollInfo.RollDate - time.Date).Days;

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

                    // Default: not rolling (this would be determined by external data in real implementation)
                    return false;
                }
            }
            catch (Exception ex)
            {
                _logger.LogError("Failed to check contract roll for {Symbol}: {Message}", symbol, ex.Message);
                throw;
            }
        }

        /// <summary>
        /// Gets the next session start time for a symbol
        /// </summary>
        /// <param name="symbol">Symbol to check</param>
        /// <returns>Date and time of next session start</returns>
        public DateTime GetNextSessionStart(string symbol)
        {
            if (string.IsNullOrEmpty(symbol))
                throw new ArgumentNullException("symbol");

            try
            {
                lock (_lock)
                {
                    var currentTime = DateTime.UtcNow;
                    var estTime = TimeZoneInfo.ConvertTimeFromUtc(currentTime,
                        TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time"));

                    if (_defaultSessions.ContainsKey(symbol))
                    {
                        var sessionTimes = _defaultSessions[symbol];
                        var start = sessionTimes.Start;
                        var end = sessionTimes.End;
                        var currentTimeOfDay = estTime.TimeOfDay;

                        // For overnight sessions
                        if (end < start)
                        {
                            // If we're in the overnight session and haven't passed the end time yet
                            if (currentTimeOfDay >= start && currentTimeOfDay < TimeSpan.FromHours(24))
                            {
                                // Next session starts tomorrow
                                return new DateTime(estTime.Year, estTime.Month, estTime.Day) + start;
                            }
                            else if (currentTimeOfDay < end)
                            {
                                // We're still in the overnight session from yesterday
                                return new DateTime(estTime.Year, estTime.Month, estTime.Day) + start;
                            }
                            else
                            {
                                // We've passed the end time, next session starts tomorrow
                                var nextDay = estTime.AddDays(1);
                                return new DateTime(nextDay.Year, nextDay.Month, nextDay.Day) + start;
                            }
                        }
                        else
                        {
                            // Regular session
                            if (currentTimeOfDay < start)
                            {
                                // Today's session hasn't started yet
                                return new DateTime(estTime.Year, estTime.Month, estTime.Day) + start;
                            }
                            else
                            {
                                // Today's session has ended, next session is tomorrow
                                var nextDay = estTime.AddDays(1);
                                return new DateTime(nextDay.Year, nextDay.Month, nextDay.Day) + start;
                            }
                        }
                    }

                    // Default to tomorrow morning if no specific session info
                    var tomorrow = estTime.AddDays(1);
                    return new DateTime(tomorrow.Year, tomorrow.Month, tomorrow.Day) + new TimeSpan(9, 0, 0);
                }
            }
            catch (Exception ex)
            {
                _logger.LogError("Failed to get next session start for {Symbol}: {Message}", symbol, ex.Message);
                throw;
            }
        }

        /// <summary>
        /// Sets contract roll information for a symbol
        /// </summary>
        /// <param name="symbol">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 SetContractRollInfo(string symbol, string activeContract, string nextContract, DateTime rollDate)
        {
            if (string.IsNullOrEmpty(symbol))
                throw new ArgumentNullException("symbol");

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

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

                    _contractRollCache[symbol] = rollInfo;

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

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

            try
            {
                lock (_lock)
                {
                    if (_contractRollCache.ContainsKey(symbol))
                    {
                        return _contractRollCache[symbol];
                    }

                    // Return default roll info if not found
                    return new ContractRollInfo(
                        symbol,
                        symbol,
                        symbol,
                        DateTime.MinValue,
                        0,
                        false
                    );
                }
            }
            catch (Exception ex)
            {
                _logger.LogError("Failed to get contract roll info for {Symbol}: {Message}", symbol, ex.Message);
                throw;
            }
        }

        /// <summary>
        /// Updates session cache for a symbol
        /// </summary>
        /// <param name="symbol">Symbol to update</param>
        /// <param name="sessionInfo">Session information</param>
        public void UpdateSessionCache(string symbol, SessionInfo sessionInfo)
        {
            if (string.IsNullOrEmpty(symbol))
                throw new ArgumentNullException("symbol");
            if (sessionInfo == null)
                throw new ArgumentNullException("sessionInfo");

            try
            {
                lock (_lock)
                {
                    _sessionCache[symbol] = sessionInfo;
                }
            }
            catch (Exception ex)
            {
                _logger.LogError("Failed to update session cache for {Symbol}: {Message}", symbol, ex.Message);
                throw;
            }
        }

        /// <summary>
        /// Clears session cache for a symbol
        /// </summary>
        /// <param name="symbol">Symbol to clear cache for</param>
        public void ClearSessionCache(string symbol)
        {
            if (string.IsNullOrEmpty(symbol))
                throw new ArgumentNullException("symbol");

            try
            {
                lock (_lock)
                {
                    if (_sessionCache.ContainsKey(symbol))
                    {
                        _sessionCache.Remove(symbol);
                    }
                    if (_contractRollCache.ContainsKey(symbol))
                    {
                        _contractRollCache.Remove(symbol);
                    }

                    _logger.LogDebug("Cleared session cache for {Symbol}", symbol);
                }
            }
            catch (Exception ex)
            {
                _logger.LogError("Failed to clear session cache for {Symbol}: {Message}", symbol, ex.Message);
                throw;
            }
        }
    }
}
