using System.Globalization;
using Microsoft.Extensions.Logging.Abstractions;
using SharepointToolbox.Core.Models;
using SharepointToolbox.ViewModels;

namespace SharepointToolbox.Tests.ViewModels;

[Trait("Category", "Unit")]
public class FeatureViewModelBaseTests
{
    private class TestViewModel : FeatureViewModelBase
    {
        public TestViewModel() : base(NullLogger<FeatureViewModelBase>.Instance) { }
        public Func<CancellationToken, IProgress<OperationProgress>, Task>? OperationFunc { get; set; }
        protected override Task RunOperationAsync(CancellationToken ct, IProgress<OperationProgress> progress)
            => OperationFunc?.Invoke(ct, progress) ?? Task.CompletedTask;
    }

    [Fact]
    public async Task IsRunning_IsTrueWhileOperationExecutes_ThenFalseAfterCompletion()
    {
        var vm = new TestViewModel();
        var tcs = new TaskCompletionSource<bool>();
        bool wasRunningDuringOperation = false;

        vm.OperationFunc = async (ct, p) =>
        {
            wasRunningDuringOperation = vm.IsRunning;
            await tcs.Task;
        };

        var runTask = vm.RunCommand.ExecuteAsync(null);
        // Give run task time to start
        await Task.Delay(10);

        Assert.True(wasRunningDuringOperation);
        tcs.SetResult(true);
        await runTask;

        Assert.False(vm.IsRunning);
    }

    [Fact]
    public async Task ProgressValue_AndStatusMessage_UpdateViaIProgress()
    {
        var vm = new TestViewModel();
        int midProgress = -1;
        string? midStatus = null;

        vm.OperationFunc = async (ct, progress) =>
        {
            progress.Report(new OperationProgress(50, 100, "halfway"));
            // Let the Progress<T> callback dispatch before sampling.
            await Task.Delay(20, ct);
            midProgress = vm.ProgressValue;
            midStatus   = vm.StatusMessage;
        };

        await vm.RunCommand.ExecuteAsync(null);

        // Mid-operation snapshot confirms IProgress reaches bound properties.
        // Post-completion, FeatureViewModelBase snaps to 100% / "Complete"
        // so stale "Scanning X" labels don't linger after a successful run.
        Assert.Equal(50, midProgress);
        Assert.Equal("halfway", midStatus);
    }

    [Fact]
    public async Task CancelCommand_DuringOperation_SetsStatusMessageToCancelled()
    {
        // Ensure EN culture so TranslationSource resolves "Operation cancelled"
        var prev = CultureInfo.CurrentUICulture;
        CultureInfo.CurrentUICulture = CultureInfo.GetCultureInfo("en");
        try
        {
            var vm = new TestViewModel();
            var started = new TaskCompletionSource<bool>();

            vm.OperationFunc = async (ct, p) =>
            {
                started.SetResult(true);
                await Task.Delay(5000, ct); // Will be cancelled
            };

            var runTask = vm.RunCommand.ExecuteAsync(null);
            await started.Task;

            vm.CancelCommand.Execute(null);
            await runTask;

            Assert.Contains("cancel", vm.StatusMessage, StringComparison.OrdinalIgnoreCase);
            Assert.False(vm.IsRunning);
        }
        finally
        {
            CultureInfo.CurrentUICulture = prev;
        }
    }

    [Fact]
    public async Task OperationCanceledException_IsCaughtGracefully_IsRunningBecomesFalse()
    {
        var vm = new TestViewModel();
        vm.OperationFunc = (ct, p) => throw new OperationCanceledException();

        // Should not throw
        await vm.RunCommand.ExecuteAsync(null);

        Assert.False(vm.IsRunning);
    }

    [Fact]
    public async Task ExceptionDuringOperation_SetsStatusMessageToErrorText_IsRunningBecomesFalse()
    {
        var vm = new TestViewModel();
        vm.OperationFunc = (ct, p) => throw new InvalidOperationException("test error");

        await vm.RunCommand.ExecuteAsync(null);

        Assert.False(vm.IsRunning);
        Assert.Contains("test error", vm.StatusMessage, StringComparison.OrdinalIgnoreCase);
    }

    [Fact]
    public async Task RunCommand_CannotBeInvoked_WhileIsRunning()
    {
        var vm = new TestViewModel();
        var tcs = new TaskCompletionSource<bool>();

        vm.OperationFunc = async (ct, p) => await tcs.Task;

        var runTask = vm.RunCommand.ExecuteAsync(null);
        await Task.Delay(10); // Let it start

        Assert.False(vm.RunCommand.CanExecute(null));

        tcs.SetResult(true);
        await runTask;

        Assert.True(vm.RunCommand.CanExecute(null));
    }
}
