using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System.Timers;
using e_suite.Scheduler.Jobs;
using e_suite.Scheduler.Scheduler;
using eSuite.Core.Clock;

namespace e_suite.Scheduler;

public class TimedHostedService : IHostedService
{
    private readonly List<ScheduledJob> _jobs = [];
    private readonly ILogger _logger;
    private readonly IServiceProvider _serviceProvider;
    private readonly IClock _clock;
    private readonly System.Timers.Timer _timer;

    public TimedHostedService(ILogger logger, IServiceProvider serviceProvider, IClock clock    )
    {
        _timer = new System.Timers.Timer
        {
            Interval = 10000,
            AutoReset = true,
        };

        _timer.Elapsed += ExecuteTimer;

        _logger = logger;
        _serviceProvider = serviceProvider;
        _clock = clock;
    }

    public Task StartAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("Timed Background Service is starting.");

        RegisterJobs();

        _timer.Enabled = true;
        ExecuteTimer(_timer, null!);
        return Task.CompletedTask;
    }

    private void RegisterJobs()
    {
        RegisterJob<NightlyCleanUp>();
        RegisterJob<NightlySigmaImport>();
    }

    private void RegisterJob<T>() where T : IJob
    {
        var schedule = GetSchedule(typeof(T));

        var scheduledJob = new ScheduledJob(_clock)
        {
            Schedule = schedule,
#pragma warning disable CS8601 //Null reference is checked for so no need to worry about it here.
            Job = _serviceProvider.GetService(typeof(T)) as IJob,
#pragma warning restore CS8601
            LastExecuted = _clock.GetNow
        };

        if (scheduledJob.Job == null)
            throw new ArgumentNullException(nameof(scheduledJob.Job), "job cannot be null");

        _jobs.Add(scheduledJob);
    }

    private static ISchedule GetSchedule(Type type)
    {
        var attributes = type.GetCustomAttributes(false);

        foreach (var attribute in attributes)
        {
            if (attribute is ISchedule schedule)
                return schedule;
        }

        throw new InvalidOperationException($"{type.FullName} does not contain a valid Schedule attribute");
    }

    private void ExecuteTimer(object? sender, ElapsedEventArgs e)
    {
        var now = _clock.GetNow;

        var taskList = new List<Task>();
        foreach (var job in _jobs.Where(job => job.NextScheduledExecution <= now))
        {
            job.LastExecuted = now;
            taskList.Add(ExecuteJobAsync(job.Job));
        }

        Task.WhenAll(taskList).GetAwaiter().GetResult();
    }

    private async Task ExecuteJobAsync(IJob job)
    {
        _logger.LogInformation("Executing {FullName}", job.GetType().FullName);
        await job.ExecuteAsync();
        _logger.LogInformation("Executing {FullName} - Completed", job.GetType().FullName);
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("Timed Background Service is stopping.");

        _jobs.Clear();
        return Task.CompletedTask;
    }
}