using System;
using System.Threading;
using System.Threading.Tasks;
using CUE4Parse.UE4.Exceptions;
using FModel.Framework;
using FModel.Services;
using FModel.Views.Resources.Controls;
using Serilog;

namespace FModel.ViewModels;

public class ThreadWorkerViewModel : ViewModel
{
    private bool _statusChangeAttempted;
    public bool StatusChangeAttempted
    {
        get => _statusChangeAttempted;
        private set => SetProperty(ref _statusChangeAttempted, value);
    }

    private bool _operationCancelled;
    public bool OperationCancelled
    {
        get => _operationCancelled;
        private set => SetProperty(ref _operationCancelled, value);
    }

    private CancellationTokenSource _currentCancellationTokenSource;
    public CancellationTokenSource CurrentCancellationTokenSource
    {
        get => _currentCancellationTokenSource;
        set
        {
            if (_currentCancellationTokenSource == value) return;
            SetProperty(ref _currentCancellationTokenSource, value);
            RaisePropertyChanged("CanBeCanceled");
        }
    }

    public bool CanBeCanceled => CurrentCancellationTokenSource != null;

    private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
    private readonly AsyncQueue<Action<CancellationToken>> _jobs;

    public ThreadWorkerViewModel()
    {
        _jobs = new AsyncQueue<Action<CancellationToken>>();
    }

    public async Task Begin(Action<CancellationToken> action)
    {
        if (_applicationView.CUE4Parse.IsSnooperOpen)
            _applicationView.CUE4Parse.SnooperViewer.Close();
        else if (!_applicationView.Status.IsReady)
        {
            SignalOperationInProgress();
            return;
        }

        CurrentCancellationTokenSource ??= new CancellationTokenSource();
        _jobs.Enqueue(action);
        await ProcessQueues();
    }

    public void Cancel()
    {
        if (!CanBeCanceled)
        {
            SignalOperationInProgress();
            return;
        }

        CurrentCancellationTokenSource.Cancel();
    }

    private async Task ProcessQueues()
    {
        if (_jobs.Count > 0)
        {
            _applicationView.Status.SetStatus(EStatusKind.Loading);
            await foreach (var job in _jobs)
            {
                try
                {
                    // will end in "catch" if canceled
                    await Task.Run(() => job(CurrentCancellationTokenSource.Token));
                }
                catch (OperationCanceledException)
                {
                    _applicationView.Status.SetStatus(EStatusKind.Stopped);
                    if (_applicationView.CUE4Parse.IsSnooperOpen)
                        _applicationView.CUE4Parse.SnooperViewer.Close();
                    CurrentCancellationTokenSource = null; // kill token
                    OperationCancelled = true;
                    OperationCancelled = false;
                    return;
                }
                catch (Exception e)
                {
                    _applicationView.Status.SetStatus(EStatusKind.Failed);
                    CurrentCancellationTokenSource = null; // kill token

                    Log.Error("{Exception}", e);
                    switch (e)
                    {
                        case MappingException:
                            FLogger.Append(ELog.Error, () =>
                            {
                                FLogger.Text("Package has unversioned properties but mapping file (.usmap) is missing, can't serialize. See: ", Constants.WHITE);
                                FLogger.Link("→ link ←", Constants.MAPPING_ISSUE_LINK, true);
                            });
                            break;
                        case VersionException v: // Error might be unrelated to version, but it's usually the case
                            FLogger.Append(ELog.Error, () =>
                            {
                                FLogger.Text(v.Message[..^1] + ", can't serialize. Make sure the correct UE version is configured. See: ", Constants.WHITE);
                                FLogger.Link("→ link ←", Constants.VERSION_ISSUE_LINK, true);
                            });
                            break;
                        default:
                            FLogger.Append(e);
                            break;
                    }
                    return;
                }
            }

            _applicationView.Status.SetStatus(EStatusKind.Completed);
            CurrentCancellationTokenSource = null; // kill token
        }
    }

    public void SignalOperationInProgress()
    {
        StatusChangeAttempted = true;
        StatusChangeAttempted = false;
    }
}
