using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;

namespace KArtSell.Modules.ModelOperations.ShadowRun.Services;

/// <summary>
/// Returns trading session dates for Korea Exchange (KRX).
/// Excludes weekends, holidays, and market closures.
/// Deterministic: same input → same output (no stochastic edge cases).
/// Cached: Loaded once at app startup; updated annually.
/// </summary>
public sealed class MarketCalendarService : IMarketCalendarService
{
    private readonly IMemoryCache _cache;
    private readonly ILogger<MarketCalendarService> _logger;

    private const string CacheKey = "krx:trading_sessions:full";
    private const int CacheDurationDays = 365;

    private static readonly Action<ILogger, int, Exception?> LogLoaded =
        LoggerMessage.Define<int>(
            LogLevel.Information,
            new EventId(1, nameof(LogLoaded)),
            "Market calendar loaded: {SessionCount} trading sessions");

    public MarketCalendarService(IMemoryCache cache, ILogger<MarketCalendarService> logger)
    {
        _cache = cache;
        _logger = logger;
    }

    /// <summary>
    /// Get trading sessions between startDate and endDate (inclusive).
    /// Returns ordered list, excludes weekends and holidays.
    /// </summary>
    public async Task<IReadOnlyList<DateOnly>> GetTradingSessionsAsync(
        DateOnly startDate,
        DateOnly endDate,
        CancellationToken cancellationToken)
    {
        await Task.Delay(10, cancellationToken);  // Async marker

        // Load full calendar (cached)
        var allSessions = GetOrLoadFullCalendar();

        // Filter to requested window
        var filtered = allSessions
            .Where(d => d >= startDate && d <= endDate)
            .ToList();

        return filtered.AsReadOnly();
    }

    private IReadOnlyList<DateOnly> GetOrLoadFullCalendar()
    {
        if (_cache.TryGetValue(CacheKey, out IReadOnlyList<DateOnly>? cached))
        {
            return cached!;
        }

        var sessions = GenerateTraditionalCalendar();
        var cacheOptions = new MemoryCacheEntryOptions
        {
            AbsoluteExpirationRelativeToNow = TimeSpan.FromDays(CacheDurationDays)
        };
        _cache.Set(CacheKey, sessions, cacheOptions);

        LogLoaded(_logger, sessions.Count, null);
        return sessions;
    }

    /// <summary>
    /// Generate trading calendar: weekdays excluding KRX holidays.
    /// For production: fetch from KRX API or external calendar service.
    /// </summary>
    private IReadOnlyList<DateOnly> GenerateTraditionalCalendar()
    {
        var sessions = new List<DateOnly>();

        // Define KRX holidays (simplified for 2024–2026)
        var holidays = new HashSet<DateOnly>
        {
            // 2024
            new(2024, 1, 1),   // New Year
            new(2024, 2, 9),   // Lunar New Year Eve
            new(2024, 2, 10),  // Lunar New Year
            new(2024, 2, 11),  // Lunar New Year Holiday
            new(2024, 2, 12),  // Lunar New Year Holiday
            new(2024, 3, 1),   // Independence Movement Day
            new(2024, 4, 10),  // Parliamentary Election
            new(2024, 5, 5),   // Children's Day
            new(2024, 5, 6),   // Temporary Holiday (following Sunday)
            new(2024, 5, 15),  // Buddha's Birthday
            new(2024, 6, 6),   // Memorial Day
            new(2024, 8, 15),  // Liberation Day
            new(2024, 9, 16),  // Chuseok Eve
            new(2024, 9, 17),  // Chuseok
            new(2024, 9, 18),  // Chuseok Holiday
            new(2024, 10, 3),  // National Foundation Day
            new(2024, 10, 9),  // Hangeul Day
            new(2024, 12, 25), // Christmas

            // 2025
            new(2025, 1, 1),   // New Year
            new(2025, 1, 28),  // Lunar New Year Eve
            new(2025, 1, 29),  // Lunar New Year
            new(2025, 1, 30),  // Lunar New Year Holiday
            new(2025, 3, 1),   // Independence Movement Day
            new(2025, 4, 11),  // Parliamentary Election
            new(2025, 5, 5),   // Children's Day
            new(2025, 5, 6),   // Temporary Holiday
            new(2025, 5, 15),  // Buddha's Birthday
            new(2025, 6, 6),   // Memorial Day
            new(2025, 8, 15),  // Liberation Day
            new(2025, 9, 5),   // Chuseok Eve
            new(2025, 9, 6),   // Chuseok
            new(2025, 9, 7),   // Chuseok Holiday
            new(2025, 10, 3),  // National Foundation Day
            new(2025, 10, 9),  // Hangeul Day
            new(2025, 12, 25), // Christmas

            // 2026
            new(2026, 1, 1),   // New Year
            new(2026, 2, 16),  // Lunar New Year Eve
            new(2026, 2, 17),  // Lunar New Year
            new(2026, 2, 18),  // Lunar New Year Holiday
            new(2026, 3, 1),   // Independence Movement Day
            new(2026, 5, 5),   // Children's Day
            new(2026, 5, 15),  // Buddha's Birthday
            new(2026, 6, 6),   // Memorial Day
            new(2026, 8, 15),  // Liberation Day
            new(2026, 9, 24),  // Chuseok Eve
            new(2026, 9, 25),  // Chuseok
            new(2026, 9, 26),  // Chuseok Holiday
            new(2026, 10, 3),  // National Foundation Day
            new(2026, 10, 9),  // Hangeul Day
            new(2026, 12, 25), // Christmas
        };

        // Generate weekdays excluding holidays
        var startDate = new DateOnly(2020, 1, 1);
        var endDate = new DateOnly(2027, 12, 31);

        for (var d = startDate; d <= endDate; d = d.AddDays(1))
        {
            if (d.DayOfWeek != DayOfWeek.Saturday
                && d.DayOfWeek != DayOfWeek.Sunday
                && !holidays.Contains(d))
            {
                sessions.Add(d);
            }
        }

        return sessions.AsReadOnly();
    }
}
