using Mercados.Infrastructure.DataFetchers;
using Cronos;
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;
        private readonly IConfiguration _configuration;
        // Diccionario para rastrear la última vez que se ejecutó una tarea diaria
        // y evitar que se ejecute múltiples veces si el servicio se reinicia.
        private readonly Dictionary _lastDailyRun = new();
        public DataFetchingService(ILogger logger, IServiceProvider serviceProvider,IConfiguration configuration)
        {
            _logger = logger;
            _serviceProvider = serviceProvider;
            _configuration = configuration;
            
            // Se define explícitamente la zona horaria de Argentina.
            // Esto asegura que los cálculos de tiempo sean correctos, sin importar
            // la configuración de zona horaria del servidor donde se ejecute el worker.
            try
            {
                // El ID estándar para Linux y macOS
                _argentinaTimeZone = TimeZoneInfo.FindSystemTimeZoneById("America/Argentina/Buenos_Aires");
            }
            catch (TimeZoneNotFoundException)
            {
                // El ID equivalente para Windows
                _argentinaTimeZone = TimeZoneInfo.FindSystemTimeZoneById("Argentina Standard Time");
            }
        }
        /// 
        /// 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);
            // Se recomienda una ejecución inicial para poblar la base de datos inmediatamente
            // al iniciar el servicio, en lugar de esperar al primer horario programado.
            //await RunAllFetchersAsync(stoppingToken);
            // PeriodicTimer es una forma moderna y eficiente de crear un bucle de "tic-tac"
            // sin bloquear un hilo con Task.Delay.
            using var timer = new PeriodicTimer(TimeSpan.FromMinutes(1));
            // El bucle se ejecuta cada minuto mientras el servicio no reciba una señal de detención.
            while (!stoppingToken.IsCancellationRequested && await timer.WaitForNextTickAsync(stoppingToken))
            {
                await RunScheduledTasksAsync(stoppingToken);
            }
        }
        /// 
        /// Revisa la hora actual y ejecuta las tareas que coincidan con su horario programado.
        /// 
        private async Task RunScheduledTasksAsync(CancellationToken stoppingToken)
        {
            var utcNow = DateTime.UtcNow;
            // Obtenemos las expresiones Cron desde la configuración
            string? agroSchedule = _configuration["Schedules:MercadoAgroganadero"];
            string? bcrSchedule = _configuration["Schedules:BCR"];
            string? bolsasSchedule = _configuration["Schedules:Bolsas"];
            // Comprobamos cada una antes de usarla
            if (!string.IsNullOrEmpty(agroSchedule))
            {
                await TryRunDailyTaskAsync("MercadoAgroganadero", agroSchedule, utcNow, stoppingToken);
            }
            else
            {
                _logger.LogWarning("No se encontró la configuración de horario para 'MercadoAgroganadero' en appsettings.json.");
            }
            if (!string.IsNullOrEmpty(bcrSchedule))
            {
                await TryRunDailyTaskAsync("BCR", bcrSchedule, utcNow, stoppingToken);
            }
            else
            {
                _logger.LogWarning("No se encontró la configuración de horario para 'BCR' en appsettings.json.");
            }
            if (!string.IsNullOrEmpty(bolsasSchedule))
            {
                await TryRunRecurringTaskAsync(new[] { "YahooFinance", "Finnhub" }, bolsasSchedule, utcNow, stoppingToken);
            }
            else
            {
                _logger.LogWarning("No se encontró la configuración de horario para 'Bolsas' en appsettings.json.");
            }
            // --- ^ FIN DE LA CORRECCIÓN DE NULABILIDAD ^ ---
        }
        /// 
        /// Comprueba y ejecuta una tarea que debe correr solo una vez al día.
        /// 
        private async Task TryRunDailyTaskAsync(string taskName, string cronExpression, DateTime utcNow, CancellationToken stoppingToken)
        {
            var cron = CronExpression.Parse(cronExpression);
            var nextOccurrence = cron.GetNextOccurrence(utcNow.AddMinutes(-1));
            if (nextOccurrence.HasValue && nextOccurrence.Value <= utcNow)
            {
                if (HasNotRunToday(taskName))
                {
                    await RunFetcherByNameAsync(taskName, stoppingToken);
                    _lastDailyRun[taskName] = TimeZoneInfo.ConvertTimeFromUtc(utcNow, _argentinaTimeZone).Date;
                }
            }
        }
        /// 
        /// Comprueba y ejecuta una tarea que puede correr múltiples veces al día.
        /// 
        private async Task TryRunRecurringTaskAsync(string[] taskNames, string cronExpression, DateTime utcNow, CancellationToken stoppingToken)
        {
            // Añadimos 'IncludeSeconds' para que la comparación sea precisa y no se ejecute dos veces en el mismo minuto.
            var cron = CronExpression.Parse(cronExpression, CronFormat.IncludeSeconds);
            // Comprobamos si hubo una ocurrencia en el último minuto.
            var nextOccurrence = cron.GetNextOccurrence(utcNow.AddMinutes(-1));
            
            if (nextOccurrence.HasValue && nextOccurrence.Value <= utcNow)
            {
                _logger.LogInformation("Ventana de ejecución recurrente detectada para: {Tasks}", string.Join(", ", taskNames));
                foreach (var taskName in taskNames)
                {
                    await RunFetcherByNameAsync(taskName, stoppingToken);
                }
            }
        }
        
        /// 
        /// Ejecuta un fetcher específico por su nombre. Utiliza un scope de DI para gestionar
        /// correctamente el ciclo de vida de los servicios (como las conexiones a la BD).
        /// 
        private async Task RunFetcherByNameAsync(string sourceName, CancellationToken stoppingToken)
        {
            if (stoppingToken.IsCancellationRequested) return;
            _logger.LogInformation("Intentando ejecutar fetcher: {sourceName}", sourceName);
            
            // Crea un "scope" de servicios. Todos los servicios "scoped" (como los repositorios)
            // se crearán de nuevo para esta ejecución y se desecharán al final, evitando problemas.
            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)
                {
                    _logger.LogError("Falló la ejecución del fetcher {sourceName}: {message}", sourceName, message);
                }
            }
            else
            {
                _logger.LogWarning("No se encontró un fetcher con el nombre: {sourceName}", sourceName);
            }
        }
        /// 
        /// Ejecuta todos los fetchers al iniciar el servicio. Esto es útil para poblar
        /// la base de datos inmediatamente al arrancar el worker.
        /// 
        /*
        private async Task RunAllFetchersAsync(CancellationToken stoppingToken)
        {
            _logger.LogInformation("Ejecutando todos los fetchers al iniciar...");
            using var scope = _serviceProvider.CreateScope();
            var fetchers = scope.ServiceProvider.GetRequiredService>();
            foreach (var fetcher in fetchers)
            {
                if (stoppingToken.IsCancellationRequested) break;
                await RunFetcherByNameAsync(fetcher.SourceName, stoppingToken);
            }
        }
        */
        
        #region Funciones de Ayuda para la Planificación
        private bool HasNotRunToday(string taskName)
        {
            return !_lastDailyRun.ContainsKey(taskName) || _lastDailyRun[taskName].Date < TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, _argentinaTimeZone).Date;
        }
        #endregion
    }
}