using System;
using System.Threading;
using System.Threading.Tasks;
using Shouldly;
using Xunit;
using ZB.MOM.WW.GRAccess.Cli.Session;

namespace ZB.MOM.WW.GRAccess.Cli.Tests.Session
{
    public class StaComThreadTests : IDisposable
    {
        private readonly StaComThread _sut;

        public StaComThreadTests()
        {
            _sut = new StaComThread();
            _sut.Start();
        }

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

        [Fact]
        public async Task RunAsync_ExecutesOnStaThread()
        {
            var apartmentState = await _sut.RunAsync(() => Thread.CurrentThread.GetApartmentState());

            apartmentState.ShouldBe(ApartmentState.STA);
        }

        [Fact]
        public async Task RunAsync_ReturnsResult()
        {
            var result = await _sut.RunAsync(() => 42);

            result.ShouldBe(42);
        }

        [Fact]
        public async Task RunAsync_PropagatesException()
        {
            var ex = await Should.ThrowAsync<InvalidOperationException>(
                _sut.RunAsync<int>(() => throw new InvalidOperationException("test error")));

            ex.Message.ShouldBe("test error");
        }

        [Fact]
        public async Task RunAsync_VoidAction_Completes()
        {
            var executed = false;

            await _sut.RunAsync(() => { executed = true; });

            executed.ShouldBeTrue();
        }

        [Fact]
        public async Task RunAsync_VoidAction_PropagatesException()
        {
            var ex = await Should.ThrowAsync<InvalidOperationException>(
                _sut.RunAsync(() => throw new InvalidOperationException("void error")));

            ex.Message.ShouldBe("void error");
        }

        [Fact]
        public async Task RunAsync_MultipleWorkItems_ExecuteInOrder()
        {
            var results = new int[3];

            await _sut.RunAsync(() => { results[0] = 1; });
            await _sut.RunAsync(() => { results[1] = 2; });
            await _sut.RunAsync(() => { results[2] = 3; });

            results.ShouldBe(new[] { 1, 2, 3 });
        }

        [Fact]
        public async Task RunAsync_AllWorkRunsOnSameThread()
        {
            var threadId1 = await _sut.RunAsync(() => Thread.CurrentThread.ManagedThreadId);
            var threadId2 = await _sut.RunAsync(() => Thread.CurrentThread.ManagedThreadId);

            threadId1.ShouldBe(threadId2);
        }

        [Fact]
        public void Dispose_CanBeCalledMultipleTimes()
        {
            var thread = new StaComThread();
            thread.Start();
            thread.Dispose();

            Should.NotThrow(() => thread.Dispose());
        }

        [Fact]
        public void RunAsync_AfterDispose_ThrowsObjectDisposed()
        {
            var thread = new StaComThread();
            thread.Start();
            thread.Dispose();

            Should.Throw<ObjectDisposedException>(() => thread.RunAsync(() => { }));
        }
    }
}
