using JobScheduler.Tasks;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

namespace JobScheduler.Scheduling;

/// <summary>
/// Loads <c>Jobs:Tasks</c> and runs each enabled task on its own interval.
/// </summary>
public sealed class JobSchedulerHostedService : BackgroundService
{
    private readonly IConfiguration _configuration;
    private readonly IReadOnlyDictionary<string, IJobTask> _tasksByType;
    private readonly ILogger<JobSchedulerHostedService> _logger;

    public JobSchedulerHostedService(
        IConfiguration configuration,
        IEnumerable<IJobTask> tasks,
        ILogger<JobSchedulerHostedService> logger)
    {
        _configuration = configuration;
        _tasksByType = tasks.ToDictionary(t => t.TaskType, t => t, StringComparer.OrdinalIgnoreCase);
        _logger = logger;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        var section = _configuration.GetSection("Jobs:Tasks");
        var children = section.GetChildren().ToList();

        if (children.Count == 0)
        {
            _logger.LogWarning("No Jobs:Tasks configured; scheduler idle.");
            await Task.Delay(Timeout.InfiniteTimeSpan, stoppingToken);
            return;
        }

        var loops = new List<Task>();
        foreach (var taskSection in children)
        {
            if (!taskSection.GetValue("Enabled", true))
            {
                _logger.LogInformation("Job task disabled: {Section}", taskSection.Path);
                continue;
            }

            var taskType = taskSection["TaskType"];
            if (string.IsNullOrWhiteSpace(taskType))
            {
                _logger.LogWarning("Skipping task without TaskType at {Section}", taskSection.Path);
                continue;
            }

            if (!_tasksByType.TryGetValue(taskType, out var task))
            {
                _logger.LogError("No IJobTask registered for TaskType '{TaskType}'.", taskType);
                continue;
            }

            var interval = ParseInterval(taskSection["Interval"]);
            var parameters = taskSection.GetSection("Parameters");

            loops.Add(RunTaskLoopAsync(taskType, task, parameters, interval, stoppingToken));
        }

        if (loops.Count == 0)
        {
            _logger.LogWarning("No enabled job tasks to run.");
            await Task.Delay(Timeout.InfiniteTimeSpan, stoppingToken);
            return;
        }

        await Task.WhenAll(loops);
    }

    private async Task RunTaskLoopAsync(
        string taskType,
        IJobTask task,
        IConfiguration parameters,
        TimeSpan interval,
        CancellationToken stoppingToken)
    {
        _logger.LogInformation(
            "Starting job loop for {TaskType} every {Interval}.",
            taskType,
            interval);

        try
        {
            while (!stoppingToken.IsCancellationRequested)
            {
                try
                {
                    await task.ExecuteAsync(parameters, stoppingToken);
                }
                catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)
                {
                    throw;
                }
                catch (Exception ex)
                {
                    _logger.LogError(ex, "Job task {TaskType} failed.", taskType);
                }

                if (interval <= TimeSpan.Zero)
                {
                    _logger.LogWarning(
                        "Job task {TaskType} has non-positive Interval; sleeping 1 hour.",
                        taskType);
                    await Task.Delay(TimeSpan.FromHours(1), stoppingToken);
                    continue;
                }

                await Task.Delay(interval, stoppingToken);
            }
        }
        catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)
        {
            _logger.LogInformation("Job loop for {TaskType} cancelled.", taskType);
        }
    }

    private static TimeSpan ParseInterval(string? value)
    {
        if (string.IsNullOrWhiteSpace(value))
        {
            return TimeSpan.FromHours(1);
        }

        return TimeSpan.TryParse(value, out var ts) ? ts : TimeSpan.FromHours(1);
    }
}
