using AIStudio.Settings;
using AIStudio.Settings.DataModel;
using AIStudio.Tools.PluginSystem;

using Microsoft.AspNetCore.Components;

namespace AIStudio.Tools.Services;

public sealed class UpdateService : BackgroundService, IMessageBusReceiver
{
    private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(UpdateService).Namespace, nameof(UpdateService));
    
    private static bool IS_INITIALIZED;

    private readonly SettingsManager settingsManager;
    private readonly MessageBus messageBus;
    private readonly RustService rust;
    private readonly UpdatePolicy updatePolicy;
    private readonly ILogger<UpdateService> logger;
    
    public UpdateService(MessageBus messageBus, SettingsManager settingsManager, RustService rust, UpdatePolicy updatePolicy, ILogger<UpdateService> logger)
    {
        this.settingsManager = settingsManager;
        this.messageBus = messageBus;
        this.rust = rust;
        this.updatePolicy = updatePolicy;
        this.logger = logger;

        this.messageBus.RegisterComponent(this);
        this.ApplyFilters([], [ Event.USER_SEARCH_FOR_UPDATE ]);
    }
    
    #region Overrides of BackgroundService

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        //
        // Wait until the app is fully initialized.
        //
        while (!stoppingToken.IsCancellationRequested && !IS_INITIALIZED)
            await Task.Delay(TimeSpan.FromSeconds(3), stoppingToken);

        DateTimeOffset? lastAutomaticCheck = null;
        while (!stoppingToken.IsCancellationRequested)
        {
            var interval = this.GetCurrentUpdateInterval();
            if (this.updatePolicy.AllowsAutomaticChecks &&
                (
                    lastAutomaticCheck is null ||
                    interval != Timeout.InfiniteTimeSpan && DateTimeOffset.UtcNow - lastAutomaticCheck >= interval)
                )
            {
                await this.CheckForUpdate();
                lastAutomaticCheck = DateTimeOffset.UtcNow;
            }

            await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);
        }
    }
    
    public override async Task StopAsync(CancellationToken cancellationToken)
    {
        this.messageBus.Unregister(this);
        await base.StopAsync(cancellationToken);
    }

    #endregion

    #region Implementation of IMessageBusReceiver

    public string ComponentName => nameof(UpdateService);

    public async Task ProcessMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data)
    {
        switch (triggeredEvent)
        {
            case Event.USER_SEARCH_FOR_UPDATE:
                if (this.updatePolicy.AllowsManualChecks)
                    await this.CheckForUpdate(notifyUserWhenNoUpdate: true);
                break;
        }
    }
    
    public Task<TResult?> ProcessMessageWithResult<TPayload, TResult>(ComponentBase? sendingComponent, Event triggeredEvent, TPayload? data)
    {
        return Task.FromResult<TResult?>(default);
    }

    #endregion

    private async Task CheckForUpdate(bool notifyUserWhenNoUpdate = false)
    {
        if(!IS_INITIALIZED)
            return;
        
        var response = await this.rust.CheckForUpdate();
        if (response.Error)
        {
            this.logger.LogWarning("Update check failed. The updater did not return a usable result.");

            if (notifyUserWhenNoUpdate)
            {
                await this.messageBus.SendError(new(Icons.Material.Filled.Error, TB("Failed to check for updates. Please try again later.")));
            }

            return;
        }

        if (response.UpdateIsAvailable)
        {
            // ReSharper disable RedundantAssignment
            var isDevEnvironment = false;
            #if DEBUG
            isDevEnvironment = true;
            #endif
            // ReSharper restore RedundantAssignment
            
            if (!isDevEnvironment && this.settingsManager.ConfigurationData.App.UpdateInstallation is UpdateInstallation.AUTOMATIC)
            {
                if (!this.updatePolicy.AllowsInstallations)
                    return;

                try
                {
                    await this.messageBus.SendMessage<bool>(null, Event.INSTALL_UPDATE);
                    await this.rust.InstallUpdate();
                }
                catch (Exception)
                {
                    await this.messageBus.SendError(new(Icons.Material.Filled.Error, TB("Failed to install update automatically. Please try again manually.")));
                }
            }
            else
                await this.messageBus.SendMessage(null, Event.UPDATE_AVAILABLE, response);
        }
        else
        {
            if (notifyUserWhenNoUpdate)
            {
                await this.messageBus.SendInfo(new(Icons.Material.Filled.Update, TB("No update found.")));
            }
        }
    }

    private TimeSpan GetCurrentUpdateInterval() => this.settingsManager.ConfigurationData.App.UpdateInterval switch
    {
        UpdateInterval.ONCE_STARTUP => Timeout.InfiniteTimeSpan,
        UpdateInterval.HOURLY => TimeSpan.FromHours(1),
        UpdateInterval.DAILY => TimeSpan.FromDays(1),
        UpdateInterval.WEEKLY => TimeSpan.FromDays(7),
        
        _ => Timeout.InfiniteTimeSpan
    };
    
    /// <summary>
    /// Signals that the Blazor UI is ready, so queued update notifications can be shown.
    /// </summary>
    public static void MarkBlazorReady() => IS_INITIALIZED = true;
}