using Microsoft.Extensions.Diagnostics.HealthChecks;
using Indexer.Models;
using Indexer.Exceptions;
using Quartz;
using Quartz.Impl;
public class RunOnceCall : ICall
{
    public ILogger _logger;
    public bool IsEnabled { get; set; }
    public bool IsExecuting { get; set; }
    public string Name { get; set; }
    public Worker Worker { get; }
    public CallConfig CallConfig { get; set; }
    public DateTime? LastExecution { get; set; }
    public DateTime? LastSuccessfulExecution { get; set; }

    public RunOnceCall(Worker worker, ILogger logger, CallConfig callConfig)
    {
        Worker = worker;
        _logger = logger;
        CallConfig = callConfig;
        IsEnabled = true;
        IsExecuting = false;
        Name = callConfig.Name;
        IndexAsync();
    }

    public void Enable()
    {
        IndexAsync();
        IsEnabled = true;
    }

    public void Disable()
    {
        IsEnabled = false;
    }

    public void Dispose() {}

    public void Stop()
    {
        if (IsEnabled)
        {
            Disable();
        }
        if (IsExecuting)
        {
            Worker.CancellationTokenSource.Cancel();
        }
    }

    private async void IndexAsync()
    {
        try
        {
            DateTime beforeExecution = DateTime.Now;
            IsExecuting = true;
            try
            {
                await Task.Run(() => Worker.ScriptContainer.Update(new RunOnceCallbackInfos()));
            }
            finally
            {
                IsExecuting = false;
                LastExecution = beforeExecution;
                Worker.LastExecution = beforeExecution;
            }
            DateTime afterExecution = DateTime.Now;
            WorkerManager.UpdateCallAndWorkerTimestamps(this, Worker, beforeExecution, afterExecution);
        }
        catch (Exception ex)
        {
            _logger.LogError("Exception occurred in a Call of Worker \"{name}\": \"{ex}\"", Worker.Name, ex.Message);
        }
    }

    public HealthCheckResult HealthCheck()
    {
        return HealthCheckResult.Healthy(); // TODO implement proper healthcheck
    }

}

public class IntervalCall : ICall
{
    public System.Timers.Timer Timer;
    public Worker Worker;
    public ILogger _logger;
    public bool IsEnabled { get; set; }
    public bool IsExecuting { get; set; }
    public string Name { get; set; }
    public CallConfig CallConfig { get; set; }
    public DateTime? LastExecution { get; set; }
    public DateTime? LastSuccessfulExecution { get; set; }

    public IntervalCall(Worker worker, ILogger logger, CallConfig callConfig)
    {
        Worker = worker;
        _logger = logger;
        CallConfig = callConfig;
        IsEnabled = true;
        IsExecuting = false;
        Name = callConfig.Name;
        if (callConfig.Interval is null)
        {
            _logger.LogError("Interval not set for a Call in Worker \"{Name}\"", worker.Name);
            throw new IndexerConfigurationException($"Interval not set for a Call in Worker \"{worker.Name}\"");
        }

        Timer = new System.Timers.Timer((double)callConfig.Interval)
        {
            AutoReset = true,
            Enabled = true
        };
        DateTime now = DateTime.Now;
        Timer.Elapsed += (sender, e) =>
        {
            try
            {
                DateTime beforeExecution = DateTime.Now;
                IsExecuting = true;
                try
                {
                    worker.ScriptContainer.Update(new IntervalCallbackInfos() { sender = sender, e = e });
                }
                finally
                {
                    IsExecuting = false;
                    LastExecution = beforeExecution;
                    worker.LastExecution = beforeExecution;
                }
                DateTime afterExecution = DateTime.Now;
                WorkerManager.UpdateCallAndWorkerTimestamps(this, worker, beforeExecution, afterExecution);
            }
            catch (Exception ex)
            {
                _logger.LogError("Exception occurred in a Call of Worker \"{name}\": \"{ex}\"", worker.Name, ex.Message);
            }
        };
    }

    public void Enable()
    {
        Timer.Start();
        IsEnabled = true;
    }

    public void Disable()
    {
        Worker.ScriptContainer.Stop();
        Timer.Stop();
        IsEnabled = false;
    }
    public void Dispose()
    {
        Timer.Dispose();
    }

    public void Stop()
    {
        if (IsEnabled)
        {
            Disable();
        }
        if (IsExecuting)
        {
            Worker.CancellationTokenSource.Cancel();
        }
    }

    public HealthCheckResult HealthCheck()
    {
        if (!Worker.ScriptContainer.UpdateInfo.Successful)
        {
            _logger.LogWarning("HealthCheck revealed: The last execution of \"{name}\" was not successful", Worker.ScriptContainer.ToolSet.FilePath);
            return HealthCheckResult.Unhealthy($"HealthCheck revealed: The last execution of \"{Worker.ScriptContainer.ToolSet.FilePath}\" was not successful");
        }
        double timerInterval = Timer.Interval; // In ms
        DateTime lastRunDateTime = Worker.ScriptContainer.UpdateInfo.DateTime;
        DateTime now = DateTime.Now;
        double millisecondsSinceLastExecution = now.Subtract(lastRunDateTime).TotalMilliseconds;
        if (millisecondsSinceLastExecution >= 2 * timerInterval)
        {
            _logger.LogWarning("HealthCheck revealed: Since the last execution of \"{name}\" more than twice the interval has passed", Worker.ScriptContainer.ToolSet.FilePath);
            return HealthCheckResult.Unhealthy($"HealthCheck revealed: Since the last execution of \"{Worker.ScriptContainer.ToolSet.FilePath}\" more than twice the interval has passed");
        }
        return HealthCheckResult.Healthy();
    }

}

public class ScheduleCall : ICall
{
    public bool IsEnabled { get; set; }
    public bool IsExecuting { get; set; }
    public string Name { get; set; }
    public Worker Worker { get; }
    public JobKey JobKey { get; }
    public JobDataMap JobDataMap { get; }
    public CallConfig CallConfig { get; set; }
    private ILogger _logger { get; }
    public DateTime? LastExecution { get; set; }
    public DateTime? LastSuccessfulExecution { get; set; }
    private StdSchedulerFactory SchedulerFactory { get; }
    private IScheduler Scheduler { get; }

    public ScheduleCall(Worker worker, CallConfig callConfig, ILogger logger)
    {
        Worker = worker;
        CallConfig = callConfig;
        _logger = logger;
        IsEnabled = false;
        IsExecuting = false;
        Name = callConfig.Name;
        JobKey = new(worker.Name);
        SchedulerFactory = new();
        Scheduler = SchedulerFactory.GetScheduler(CancellationToken.None).Result;
        JobDataMap = [];
        JobDataMap["action"] = () =>
        {
            try
            {
                DateTime beforeExecution = DateTime.Now;
                IsExecuting = true;
                try
                {
                    worker.ScriptContainer.Update(new ScheduleCallbackInfos());
                }
                finally
                {
                    IsExecuting = false;
                    LastExecution = beforeExecution;
                    worker.LastExecution = beforeExecution;
                }
                DateTime afterExecution = DateTime.Now;
                WorkerManager.UpdateCallAndWorkerTimestamps(this, worker, beforeExecution, afterExecution);
            }
            catch (Exception ex)
            {
                _logger.LogError("Exception occurred in a Call of Worker \"{name}\": \"{ex}\"", worker.Name, ex.Message);
            }
        };
        CreateJob().Wait();
        Enable();
    }

    public void Enable()
    {
        if (!IsEnabled)
        {
            Scheduler.Start(CancellationToken.None).Wait();
            IsEnabled = true;
        }
    }

    public void Disable()
    {
        Scheduler.PauseAll();
        IsEnabled = false;
    }

    public void Dispose()
    {
        Scheduler.DeleteJob(JobKey);
    }

    public void Stop()
    {
        if (IsEnabled)
        {
            Disable();
        }
        if (IsExecuting)
        {
            Worker.CancellationTokenSource.Cancel();
        }
    }

    private async Task CreateJob()
    {
        if (CallConfig.Schedule is null)
        {
            throw new IndexerConfigurationException($"Interval not set for a Call in Worker \"{Worker.Name}\"");
        }
        try
        {

            await Scheduler.ScheduleJob(
                JobBuilder.Create<ActionJob>()
                .WithIdentity(JobKey)
                .Build(),
                TriggerBuilder.Create()
                .ForJob(JobKey)
                .WithIdentity(Worker.Name + "-trigger")
                .UsingJobData(JobDataMap)
                .WithCronSchedule(CallConfig.Schedule)
                .Build(),
                CancellationToken.None);
        }
        catch (FormatException)
        {
            throw new IndexerConfigurationException($"Quartz Cron expression invalid in Worker \"{Worker.Name}\" - Quartz syntax differs from classic cron");
        }
    }

    public HealthCheckResult HealthCheck()
    {
        return HealthCheckResult.Unhealthy(); // Not implemented yet
    }
}

public class FileUpdateCall : ICall
{
    public bool IsEnabled { get; set; }
    public bool IsExecuting { get; set; }
    public string Name { get; set; }
    public Worker Worker { get; }
    public CallConfig CallConfig { get; set; }
    private ILogger _logger { get; }
    private FileSystemWatcher _watcher { get; }
    public DateTime? LastExecution { get; set; }
    public DateTime? LastSuccessfulExecution { get; set; }

    public FileUpdateCall(Worker worker, CallConfig callConfig, ILogger logger)
    {
        Worker = worker;
        CallConfig = callConfig;
        _logger = logger;
        IsEnabled = true;
        IsExecuting = false;
        Name = callConfig.Name;
        if (CallConfig.Path is null)
        {
            throw new IndexerConfigurationException($"Path not set for a Call in Worker \"{Worker.Name}\"");
        }

        List<string> events = callConfig.Events ?? [];
        bool allEvents = events.Count == 0;
        List<string> filters = callConfig.Filters ?? [];
        bool includeSubdirectories = callConfig.IncludeSubdirectories ?? false;

        _watcher = new FileSystemWatcher(CallConfig.Path);
        if (allEvents || events.Contains("Created")) _watcher.Created += OnFileChanged;
        if (allEvents || events.Contains("Changed")) _watcher.Changed += OnFileChanged;
        if (allEvents || events.Contains("Deleted")) _watcher.Deleted += OnFileChanged;
        if (allEvents || events.Contains("Renamed")) _watcher.Renamed += OnFileChanged;
        foreach (string filter in filters)
        {
            _watcher.Filters.Add(filter);
        }
        _watcher.IncludeSubdirectories = includeSubdirectories;
        _watcher.EnableRaisingEvents = true;
    }

    public void Enable()
    {
        if (!IsEnabled)
        {
            IsEnabled = true;
            _watcher.EnableRaisingEvents = true;
            Index();
        }
    }

    public void Disable()
    {
        if (IsEnabled)
        {
            IsEnabled = false;
            _watcher.EnableRaisingEvents = false;
        }
    }

    public void Dispose()
    {
        _watcher.Dispose();
    }

    public void Stop()
    {
        if (IsEnabled)
        {
            Disable();
        }
        if (IsExecuting)
        {
            Worker.CancellationTokenSource.Cancel();
        }
    }

    private void OnFileChanged(object sender, FileSystemEventArgs e)
    {
        if (!IsEnabled)
        {
            return;
        }
        Index(sender, e);
    }

    private void Index(object? sender, FileSystemEventArgs? e)
    {
        try
        {
            DateTime beforeExecution = DateTime.Now;
            IsExecuting = true;
            try
            {
                Worker.ScriptContainer.Update(new FileUpdateCallbackInfos() {sender = sender, e = e});
            }
            finally
            {
                IsExecuting = false;
                LastExecution = beforeExecution;
                Worker.LastExecution = beforeExecution;
            }
            DateTime afterExecution = DateTime.Now;
            WorkerManager.UpdateCallAndWorkerTimestamps(this, Worker, beforeExecution, afterExecution);
        }
        catch (Exception ex)
        {
            _logger.LogError("Exception occurred in a Call of Worker \"{name}\": \"{ex}\"", Worker.Name, ex.Message);
        }
    }

    private void Index()
    {
        Index(null, null);
    }

    public HealthCheckResult HealthCheck()
    {
        return HealthCheckResult.Unhealthy(); // Not implemented yet
    }
}