using System.IO;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Extensions.Configuration;
using Torch.API.Managers;
using Torch.Managers;

namespace Maintenance.Managers;

public class MaintenanceScheduleManager(string storagePath) : IManager
{
    [Manager.Dependency]
    private readonly MaintenanceManager _maintenanceManager = null!;
    
    [Manager.Dependency]
    private readonly ConfigManager _configManager = null!;
    
    [Manager.Dependency]
    private readonly LanguageManager _languageManager = null!;
    
    [Manager.Dependency]
    private readonly IChatManagerServer _chatManager = null!;

    private MaintenanceSchedule _currentSchedule = MaintenanceSchedule.Default;
    
    private readonly FileInfo _scheduleFile = new(Path.Combine(storagePath, ".schedule"));

    public MaintenanceSchedule CurrentSchedule
    {
        get => _currentSchedule;
        private set
        {
            if (value != _currentSchedule)
            {
                using var file = _scheduleFile.Create();
                JsonSerializer.Serialize(file, value);
            }
            
            _currentSchedule = value;
        }
    }

    private readonly CancellationTokenSource _cancellationTokenSource = new();

    public void Attach()
    {
        Scheduler();
        
        if (!_scheduleFile.Exists) return;
        
        using (var file = _scheduleFile.OpenRead())
            _currentSchedule = JsonSerializer.Deserialize<MaintenanceSchedule>(file)!;
        
        _scheduleFile.Delete();

        if (_currentSchedule is not { StartTime: null, EndTime: not null }) return;
        
        if (!_configManager.Configuration.GetValue<bool>(ConfigKeys.ContinueEndTimerAfterRestart))
            CurrentSchedule = MaintenanceSchedule.Default;
        
        _maintenanceManager.MaintenanceEnabled = true;
    }

    private async void Scheduler()
    {
        var token = _cancellationTokenSource.Token;
        
        var timerSecondsSection = _configManager.Configuration.GetSection(ConfigKeys.TimerBroadcastForSeconds);
        var timerSeconds = timerSecondsSection.GetChildren().Select(b => b.Get<int>()).ToArray();

        using var disposable = timerSecondsSection.GetReloadToken()
            .RegisterChangeCallback(_ => timerSeconds = timerSecondsSection.GetChildren().Select(b => b.Get<int>()).ToArray(), null);
        
        while (!token.IsCancellationRequested)
        {
            await Task.Delay(2000, token);

            switch (_maintenanceManager.MaintenanceEnabled)
            {
                case false when _currentSchedule.StartTime.HasValue:
                {
                    if (timerSeconds.Any(b => Math.Abs(_currentSchedule.Time.TotalSeconds - b) <= 1))
                    {
                        _chatManager.SendMessageAsOther("Maintenance", 
                            Format(_currentSchedule.EndTime.HasValue
                                ? LangKeys.ScheduleTimerBroadcast
                                : LangKeys.StartTimerBroadcast));
                    }

                    if (_currentSchedule.Time.TotalSeconds <= 1)
                    {
                        _chatManager.SendMessageAsOther("Maintenance", Format(LangKeys.MaintenanceActivated));
                        _maintenanceManager.MaintenanceEnabled = true;
                        CurrentSchedule = CurrentSchedule with { StartTime = null };
                    }

                    break;
                }
                case true when _currentSchedule.EndTime.HasValue:
                {
                    if (timerSeconds.Any(b => Math.Abs(_currentSchedule.Duration.TotalSeconds - b) <= 1))
                    {
                        _chatManager.SendMessageAsOther("Maintenance", Format(LangKeys.EndTimerBroadcast));
                    }

                    if (_currentSchedule.Duration.TotalSeconds <= 1)
                    {
                        _chatManager.SendMessageAsOther("Maintenance", Format(LangKeys.MaintenanceDeactivated));
                        _maintenanceManager.MaintenanceEnabled = false;
                        CurrentSchedule = CurrentSchedule with { EndTime = null };
                    }

                    break;
                }
            }
        }
        
        string Format(string key) => _languageManager.Format(key, _currentSchedule);
    }

    public void Detach()
    {
        _cancellationTokenSource.Cancel();
        _cancellationTokenSource.Dispose();
    }
    
    public void ScheduleTimer(TimerType type, TimeSpan duration)
    {
        if (_maintenanceManager.MaintenanceEnabled && type == TimerType.Start)
            throw new InvalidOperationException("Maintenance is already enabled");
        
        if (type == TimerType.Start)
            CurrentSchedule = CurrentSchedule with { StartTime = DateTimeOffset.Now + duration };
        else
            CurrentSchedule = CurrentSchedule with { EndTime = DateTimeOffset.Now + duration };
    }
    
    public void ScheduleMaintenance(TimeSpan startTime, TimeSpan endTime)
    {
        if (_maintenanceManager.MaintenanceEnabled)
            throw new InvalidOperationException("Maintenance is already enabled");

        var startDateTime = DateTimeOffset.Now + startTime;
        
        CurrentSchedule = new(startDateTime, startDateTime + endTime);
    }

    public void CancelTimer(TimerType? type)
    {
        CurrentSchedule = type switch
        {
            TimerType.Start when !_currentSchedule.StartTime.HasValue => throw new InvalidOperationException(
                "No start timer running"),
            TimerType.Start => MaintenanceSchedule.Default,
            TimerType.End when !_currentSchedule.EndTime.HasValue => throw new InvalidOperationException(
                "No end timer running"),
            TimerType.End => CurrentSchedule with { EndTime = null },
            _ => MaintenanceSchedule.Default
        };
    }
}

public record MaintenanceSchedule(DateTimeOffset? StartTime, DateTimeOffset? EndTime)
{
    public static MaintenanceSchedule Default => new(null, null);

    [JsonIgnore]
    public TimeSpan Time => Round((StartTime ?? throw new InvalidOperationException("No start timer running"))
                                  - DateTimeOffset.Now);
    [JsonIgnore]
    public TimeSpan Duration => Round((EndTime ?? throw new InvalidOperationException("No end timer running"))
                                      - (StartTime ?? DateTimeOffset.Now));
    
    private static TimeSpan Round(TimeSpan timeSpan) => TimeSpan.FromSeconds(Math.Round(timeSpan.TotalSeconds, 0));
}

public enum TimerType : byte
{
    Start,
    End
}