using Cronos;
using Mercados.Infrastructure.DataFetchers;
using Mercados.Infrastructure.Services;
using Microsoft.Extensions.Configuration;
namespace Mercados.Worker
{
    /// 
    /// Servicio de fondo que orquesta la obtención de datos de diversas fuentes
    /// de forma programada y periódica.
    /// 
    public class DataFetchingService : BackgroundService
    {
        private readonly ILogger _logger;
        private readonly IServiceProvider _serviceProvider;
        private readonly TimeZoneInfo _argentinaTimeZone;
        // Almacenamos las expresiones Cron parseadas para no tener que hacerlo en cada ciclo.
        private readonly CronExpression _agroSchedule;
        private readonly CronExpression _bcrSchedule;
        private readonly CronExpression _bolsasSchedule;
        // Almacenamos la próxima ejecución calculada para cada tarea.
        private DateTime? _nextAgroRun;
        private DateTime? _nextBcrRun;
        private DateTime? _nextBolsasRun;
        // Diccionario para rastrear la hora de la última alerta ENVIADA por cada tarea.
        private readonly Dictionary _lastAlertSent = new();
        // Definimos el período de "silencio" para las alertas (ej. 4 horas).
        private readonly TimeSpan _alertSilencePeriod = TimeSpan.FromHours(4);
        public DataFetchingService(
            ILogger logger,
            IServiceProvider serviceProvider,
            IConfiguration configuration)
        {
            _logger = logger;
            _serviceProvider = serviceProvider;
            // Se define explícitamente la zona horaria de Argentina.
            try
            {
                _argentinaTimeZone = TimeZoneInfo.FindSystemTimeZoneById("America/Argentina/Buenos_Aires");
            }
            catch (TimeZoneNotFoundException)
            {
                _argentinaTimeZone = TimeZoneInfo.FindSystemTimeZoneById("Argentina Standard Time");
            }
            // Parseamos las expresiones Cron UNA SOLA VEZ, en el constructor.
            // Si una expresión es inválida o nula, el servicio fallará al iniciar, 
            // lo cual es un comportamiento deseable para alertar de una mala configuración.
            // El '!' le dice al compilador que confiamos que estos valores no serán nulos.
            _agroSchedule = CronExpression.Parse(configuration["Schedules:MercadoAgroganadero"]!);
            _bcrSchedule = CronExpression.Parse(configuration["Schedules:BCR"]!);
            _bolsasSchedule = CronExpression.Parse(configuration["Schedules:Bolsas"]!);
        }
        /// 
        /// Método principal del servicio. Se ejecuta una vez cuando el servicio arranca.
        /// 
        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            _logger.LogInformation("🚀 Servicio de Fetching iniciado a las: {time}", DateTimeOffset.Now);
            // Ejecutamos una vez al inicio para tener datos frescos inmediatamente.
            //await RunAllFetchersAsync(stoppingToken);
            // Calculamos las primeras ejecuciones programadas al arrancar.
            var utcNow = DateTime.UtcNow;
            _nextAgroRun = _agroSchedule.GetNextOccurrence(utcNow, _argentinaTimeZone);
            _nextBcrRun = _bcrSchedule.GetNextOccurrence(utcNow, _argentinaTimeZone);
            _nextBolsasRun = _bolsasSchedule.GetNextOccurrence(utcNow, _argentinaTimeZone);
            // Usamos un PeriodicTimer que "despierta" cada 30 segundos para revisar si hay tareas pendientes.
            // Un intervalo más corto aumenta la precisión del disparo de las tareas.
            using var timer = new PeriodicTimer(TimeSpan.FromSeconds(30));
            while (!stoppingToken.IsCancellationRequested && await timer.WaitForNextTickAsync(stoppingToken))
            {
                utcNow = DateTime.UtcNow;
                // Comprobamos si ha llegado el momento de la próxima ejecución para cada tarea.
                if (_nextAgroRun.HasValue && utcNow >= _nextAgroRun.Value)
                {
                    await RunFetcherByNameAsync("MercadoAgroganadero", stoppingToken);
                    // Inmediatamente después de ejecutar, calculamos la SIGUIENTE ocurrencia.
                    _nextAgroRun = _agroSchedule.GetNextOccurrence(utcNow, _argentinaTimeZone);
                }
                if (_nextBcrRun.HasValue && utcNow >= _nextBcrRun.Value)
                {
                    await RunFetcherByNameAsync("BCR", stoppingToken);
                    _nextBcrRun = _bcrSchedule.GetNextOccurrence(utcNow, _argentinaTimeZone);
                }
                if (_nextBolsasRun.HasValue && utcNow >= _nextBolsasRun.Value)
                {
                    _logger.LogInformation("Ventana de ejecución para Bolsas. Iniciando en paralelo...");
                    await Task.WhenAll(
                        RunFetcherByNameAsync("YahooFinance", stoppingToken),
                        RunFetcherByNameAsync("Finnhub", stoppingToken)
                    );
                    _nextBolsasRun = _bolsasSchedule.GetNextOccurrence(utcNow, _argentinaTimeZone);
                }
            }
        }
        /// 
        /// Ejecuta un fetcher específico por su nombre, gestionando el scope de DI y las notificaciones.
        /// 
        private async Task RunFetcherByNameAsync(string sourceName, CancellationToken stoppingToken)
        {
            if (stoppingToken.IsCancellationRequested) return;
            _logger.LogInformation("Intentando ejecutar fetcher: {sourceName}", sourceName);
            using var scope = _serviceProvider.CreateScope();
            var fetchers = scope.ServiceProvider.GetRequiredService>();
            var fetcher = fetchers.FirstOrDefault(f => f.SourceName.Equals(sourceName, StringComparison.OrdinalIgnoreCase));
            if (fetcher != null)
            {
                var (success, message) = await fetcher.FetchDataAsync();
                if (!success)
                {
                    var errorMessage = $"Falló la ejecución del fetcher {sourceName}: {message}";
                    _logger.LogError(errorMessage);
                    if (ShouldSendAlert(sourceName))
                    {
                        var notifier = scope.ServiceProvider.GetRequiredService();
                        await notifier.SendFailureAlertAsync($"Fallo Crítico en el Fetcher: {sourceName}", errorMessage, DateTime.UtcNow);
                        _lastAlertSent[sourceName] = DateTime.UtcNow;
                    }
                    else
                    {
                        _logger.LogWarning("Fallo repetido para {sourceName}. Alerta silenciada temporalmente.", sourceName);
                    }
                }
            }
            else
            {
                _logger.LogWarning("No se encontró un fetcher con el nombre: {sourceName}", sourceName);
            }
        }
        /// 
        /// Ejecuta todos los fetchers en paralelo al iniciar el servicio.
        /// 
        private async Task RunAllFetchersAsync(CancellationToken stoppingToken)
        {
            _logger.LogInformation("Ejecutando todos los fetchers al iniciar en paralelo...");
            using var scope = _serviceProvider.CreateScope();
            var fetchers = scope.ServiceProvider.GetRequiredService>();
            var tasks = fetchers.Select(fetcher => RunFetcherByNameAsync(fetcher.SourceName, stoppingToken));
            await Task.WhenAll(tasks);
            _logger.LogInformation("Ejecución inicial de todos los fetchers completada.");
        }
        #region Funciones de Ayuda para la Planificación
        /// 
        /// Determina si se debe enviar una alerta o si está en período de silencio.
        /// 
        private bool ShouldSendAlert(string taskName)
        {
            if (!_lastAlertSent.ContainsKey(taskName))
            {
                return true;
            }
            var lastAlertTime = _lastAlertSent[taskName];
            return DateTime.UtcNow.Subtract(lastAlertTime) > _alertSilencePeriod;
        }
        #endregion
    }
}