﻿using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Chill.Specs.TestSubjects;
using Chill.StateBuilders;
using FluentAssertions;
using Xunit;

namespace Chill.Specs
{
    public class GivenWhenThenSpecs : GivenWhenThen
    {
        AClass expectedAClass = new AClass();

        [Fact]
        public void When_setting_items_directly_then_it_should_be_found()
        {
            When(() => UseThe(expectedAClass));
        }

        [Fact]
        public void When_executing_later_then_action_is_not_executed_immediately()
        {
            WhenLater(() =>
            {
                throw new Exception();
            });
        }

        [Fact]
        public void When_deferring_execution_then_action_is_not_executed_immediately()
        {
            When(() =>
            {
                throw new Exception();
            }, deferredExecution: true);
        }

        [Fact]
        public void When_deferring_execution_as_property_then_action_is_not_executed_immediately()
        {
            DeferredExecution = true;
            When(() =>
            {
                throw new Exception();
            });
        }

        [Fact]
        public void When_setting_multiple_items_then_they_should_be_set()
        {
            When(() => SetAll(new AClass("1"), new AClass("2")));

            All<AClass>().Should().BeEquivalentTo(new[] {new AClass("1"), new AClass("2"),});
        }

        [Fact]
        public void When_exception_is_thrown_in_async_method_in_later_execution_expected_exception_is_filled()
        {
            Func<Task> whenActionASync = () => Task.Factory.StartNew(() =>
            {
                throw new AbandonedMutexException();
            });
            WhenLater(whenActionASync);
            CaughtException.Should().BeOfType<AbandonedMutexException>();
        }

        [Fact]
        public void When_exception_is_thrown_in_async_method_in_deferred_execution_expected_exception_is_filled()
        {
            Func<Task> whenActionASync = () => Task.Factory.StartNew(() =>
            {
                throw new AbandonedMutexException();
            });
            When(whenActionASync, deferredExecution: true);
            CaughtException.Should().BeOfType<AbandonedMutexException>();
        }

        [Fact]
        public void When_exception_is_thrown_in_later_execution_expected_exception_is_filled()
        {
            Action whenActionASync = () =>
            {
                throw new AbandonedMutexException();
            };
            WhenLater(whenActionASync);
            CaughtException.Should().BeOfType<AbandonedMutexException>();
        }

        [Fact]
        public void When_exception_is_thrown_in_deferred_execution_expected_exception_is_filled()
        {
            Action whenActionASync = () =>
            {
                throw new AbandonedMutexException();
            };
            When(whenActionASync, deferredExecution: true);
            CaughtException.Should().BeOfType<AbandonedMutexException>();
        }

        [Fact]
        public void When_deferring_execution_then_whenaction_can_be_used_to_test_for_exceptions()
        {
            When(() =>
            {
                throw new AbandonedMutexException();
            }, deferredExecution: true);
            WhenAction.Should().Throw<AbandonedMutexException>();
        }

        [Fact]
        public void When_executing_later_then_whenaction_can_be_used_to_test_for_exceptions()
        {
            WhenLater(() =>
            {
                throw new AbandonedMutexException();
            });
            WhenAction.Should().Throw<AbandonedMutexException>();
        }

        [Fact]
        public void When_later_no_exception_would_be_thrown_but_caught_exception_is_used_it_should_throw()
        {
            WhenLater(() =>
            {
            });
            Action a = () =>
            {
                Exception e = CaughtException;
            };
            a.Should().Throw<InvalidOperationException>().WithMessage("Expected exception but no exception was thrown");
        }

        [Fact]
        public void When_no_exception_is_thrown_but_expected_exception_is_used_then_caughtexceptoin_throws()
        {
            When(() =>
            {
            }, deferredExecution: true);
            Action a = () =>
            {
                Exception e = CaughtException;
            };
            a.Should().Throw<InvalidOperationException>().WithMessage("Expected exception but no exception was thrown");
        }

        [Fact]
        public void When_setting_testclass_in_container_then_it_should_be_found()
        {
            When(() => SetThe<AClass>().To(expectedAClass));

            The<AClass>().Should().Be(expectedAClass);
        }

        public class Given_several_testclasses_in_the_container : GivenWhenThen
        {
            public Given_several_testclasses_in_the_container()
            {
                Given(() =>
                {
                    SetThe<AClass>().Named("first").To(new AClass("first"));
                    SetThe<AClass>().Named("second").To(new AClass("second"));
                });
            }


            [Fact]
            public void Then_named_testclass_is_present()
            {
                TheNamed<AClass>("first").Name.Should().Be("first");
            }

            [Fact]
            public void Then_all_should_be_contain_all_testclasses()
            {
                All<AClass>().Count().Should().Be(2);
            }
        }
    }
}