using NSubstitute;
using NUnit.Framework;
using System;
using System.Threading.Tasks;
using TINK.Model.Bike.BluetoothLock;
using TINK.Model.Bikes.Bike.BluetoothLock;
using TINK.Model.Connector;
using TINK.Repository.Exception;
using TINK.Services.BluetoothLock;
using TINK.Services.BluetoothLock.Exception;
using TINK.Services.BluetoothLock.Tdo;
using TINK.Model.Services.Geolocation;
using TINK.Model.State;
using TINK.View;
using TINK.ViewModel;
using TINK.ViewModel.Bikes;
using TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler;
using TINK.Model.User;
using TINK.Repository.Request;
using TINK.Repository.Response;
using Newtonsoft.Json;
using TINK.Model.Device;

namespace TestShareeLib.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
    public class TestReservedUnknown
    {
        [TestFixture]
        public class TestBookedUnknown
        {

            /// <summary>
            /// Test construction of object.
            /// </summary>
            [Test]
            public void Testctor()
            {
                var handler = new ReservedUnknown(
                    Substitute.For<IBikeInfoMutable>(),
                    () => true, // isConnectedDelegate
                    (isConnexted) => Substitute.For<IConnector>(),
                    Substitute.For<IGeolocation>(),
                    Substitute.For<ILocksService>(),
                    () => Substitute.For<IPollingUpdateTaskManager>(),
                    Substitute.For<ISmartDevice>(),
                    Substitute.For<IViewService>(),
                    Substitute.For<IBikesViewModel>(),
                    Substitute.For<IUser>());

                // Verify prerequisites.
                Assert.AreEqual("Open lock & rent bike", handler.ButtonText);
                Assert.IsFalse(handler.IsButtonVisible);
                Assert.AreEqual("Close lock", handler.LockitButtonText);
                Assert.IsTrue(handler.IsLockitButtonVisible);
            }

            /// <summary>
            /// Use case: Opens lock
            /// Final state: Booked opened.
            /// </summary>
            [Test]
            public void TestOpen()
            {
                var bike = Substitute.For<IBikeInfoMutable>();
                var connector = Substitute.For<IConnector>();
                var command = Substitute.For<ICommand>();
                var geolocation = Substitute.For<IGeolocation>();
                var locks = Substitute.For<ILocksService>();
                var pollingManager = Substitute.For<IPollingUpdateTaskManager>();
                var viewService = Substitute.For<IViewService>();
                var bikesViewModel = Substitute.For<IBikesViewModel>();
                var activeUser = Substitute.For<IUser>();

                var handler = new ReservedUnknown(
                    bike,
                    () => true, // isConnectedDelegate
                    (isConnexted) => connector,
                    geolocation,
                    locks,
                    () => pollingManager,
                    Substitute.For<ISmartDevice>(),
                    viewService,
                    bikesViewModel,
                    activeUser);

                locks[0].OpenAsync()
                    .Returns(Task.FromResult((LockitLockingState?)LockitLockingState.Open)); // Return lock state indicating success

                bike.State.Value.Returns(InUseStateEnum.Booked);

                var subsequent = handler.HandleRequestOption1().Result;

                // Verify behaviour
                Received.InOrder(() =>
                {
                    bikesViewModel.Received(1).IsIdle = false; // GUI must be locked
                    bikesViewModel.ActionText = "One moment please...";
                    pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action
                    bikesViewModel.ActionText = "Opening lock...";
                    locks.Received()[0].OpenAsync(); // Lock must be closed
                    bikesViewModel.ActionText = "Reading charging level...";
                    locks[0].GetBatteryPercentageAsync();
                    bikesViewModel.ActionText = "Updating lock state...";
                    connector.Command.UpdateLockingStateAsync(bike, null);
                    bikesViewModel.ActionText = "Updating...";
                    pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again
                    bikesViewModel.ActionText = "";
                    bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked
                });

                // Verify state "Booked Closed" after action
                Assert.AreEqual("Close lock & return bike", subsequent.ButtonText);
                Assert.IsTrue(subsequent.IsButtonVisible);
                Assert.AreEqual("Close lock", subsequent.LockitButtonText);
                Assert.IsTrue(subsequent.IsLockitButtonVisible);
            }

            /// <summary>
            /// Use case: Open lock
            /// Final state: Same as initial state.
            /// </summary>
            [Test]
            public void TestOpenOpenFailsOutOfReachException()
            {
                var bike = Substitute.For<IBikeInfoMutable>();
                var connector = Substitute.For<IConnector>();
                var command = Substitute.For<ICommand>();
                var geolocation = Substitute.For<IGeolocation>();
                var locks = Substitute.For<ILocksService>();
                var pollingManager = Substitute.For<IPollingUpdateTaskManager>();
                var viewService = Substitute.For<IViewService>();
                var bikesViewModel = Substitute.For<IBikesViewModel>();
                var activeUser = Substitute.For<IUser>();

                var handler = new ReservedUnknown(
                    bike,
                    () => true, // isConnectedDelegate
                    (isConnexted) => connector,
                    geolocation,
                    locks,
                    () => pollingManager,
                    Substitute.For<ISmartDevice>(),
                    viewService,
                    bikesViewModel,
                    activeUser);

                locks[0].OpenAsync()
                    .Returns<Task<LockitLockingState?>>(x => throw new OutOfReachException());

                bike.State.Value.Returns(InUseStateEnum.Booked);
                bike.LockInfo.State.Returns(LockingState.Closed);

                var subsequent = handler.HandleRequestOption1().Result;

                // Verify behaviour
                Received.InOrder(() =>
                {
                    bikesViewModel.Received(1).IsIdle = false; // GUI must be locked
                    bikesViewModel.ActionText = "One moment please...";
                    pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action
                    bikesViewModel.ActionText = "Opening lock...";
                    locks.Received()[0].OpenAsync(); // Lock must be closed
                    bikesViewModel.ActionText = "";
                    viewService.DisplayAlert("Error while opening lock!", "Lock cannot be opened until bike is near.", "OK");
                    bikesViewModel.ActionText = "Updating...";
                    pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again
                    bikesViewModel.ActionText = "";
                    bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked
                });

                // Verify state "Booked Disconnected" after action
                Assert.AreEqual(nameof(BookedDisconnected), subsequent.ButtonText);
                Assert.IsFalse(subsequent.IsButtonVisible);
                Assert.AreEqual("Search lock", subsequent.LockitButtonText);
                Assert.IsTrue(subsequent.IsLockitButtonVisible);
            }

            /// <summary>
            /// Use case: Open lock
            /// Final state: Same as initial state.
            /// </summary>
            [Test]
            public void TestOpenOpenFailsCouldntOpenBoldBlockedException()
            {
                var bike = Substitute.For<IBikeInfoMutable>();
                var connector = Substitute.For<IConnector>();
                var command = Substitute.For<ICommand>();
                var geolocation = Substitute.For<IGeolocation>();
                var locks = Substitute.For<ILocksService>();
                var pollingManager = Substitute.For<IPollingUpdateTaskManager>();
                var viewService = Substitute.For<IViewService>();
                var bikesViewModel = Substitute.For<IBikesViewModel>();
                var activeUser = Substitute.For<IUser>();

                var handler = new ReservedUnknown(
                    bike,
                    () => true, // isConnectedDelegate
                    (isConnexted) => connector,
                    geolocation,
                    locks,
                    () => pollingManager,
                    Substitute.For<ISmartDevice>(),
                    viewService,
                    bikesViewModel,
                    activeUser);

                locks[0].OpenAsync()
                    .Returns<Task<LockitLockingState?>>(x => throw new CouldntOpenBoldIsBlockedException());

                bike.State.Value.Returns(InUseStateEnum.Booked);
                bike.LockInfo.State.Returns(LockingState.Closed);

                var subsequent = handler.HandleRequestOption1().Result;

                // Verify behaviour
                Received.InOrder(() =>
                {
                    bikesViewModel.Received(1).IsIdle = false; // GUI must be locked
                    bikesViewModel.ActionText = "One moment please...";
                    pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action
                    bikesViewModel.ActionText = "Opening lock...";
                    locks.Received()[0].OpenAsync(); // Lock must be closed
                    bikesViewModel.ActionText = "";
                    viewService.DisplayAlert("Error while opening lock!", "Lock is blocked. Please ensure that no obstacle prevents lock from opening and try again.", "OK");
                    bikesViewModel.ActionText = "Updating...";
                    pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again
                    bikesViewModel.ActionText = "";
                    bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked
                });

                // Verify state "Booked Unknown" after action
                Assert.AreEqual("Open lock & continue renting", subsequent.ButtonText);
                Assert.IsTrue(subsequent.IsButtonVisible);
                Assert.AreEqual("Close lock", subsequent.LockitButtonText);
                Assert.IsTrue(subsequent.IsLockitButtonVisible);
            }

            /// <summary>
            /// Use case: Open lock
            /// Final state: Same as initial state.
            /// </summary>
            [Test]
            public void TestOpenOpenFailsCouldntOpenInconsistentStateExecptionClosed()
            {
                var bike = Substitute.For<IBikeInfoMutable>();
                var connector = Substitute.For<IConnector>();
                var command = Substitute.For<ICommand>();
                var geolocation = Substitute.For<IGeolocation>();
                var locks = Substitute.For<ILocksService>();
                var pollingManager = Substitute.For<IPollingUpdateTaskManager>();
                var viewService = Substitute.For<IViewService>();
                var bikesViewModel = Substitute.For<IBikesViewModel>();
                var activeUser = Substitute.For<IUser>();

                var handler = new ReservedUnknown(
                    bike,
                    () => true, // isConnectedDelegate
                    (isConnexted) => connector,
                    geolocation,
                    locks,
                    () => pollingManager,
                    Substitute.For<ISmartDevice>(),
                    viewService,
                    bikesViewModel,
                    activeUser);

                locks[0].OpenAsync()
                    .Returns<Task<LockitLockingState?>>(x => throw new CouldntOpenInconsistentStateExecption(LockingState.Closed));

                bike.State.Value.Returns(InUseStateEnum.Booked);
                bike.LockInfo.State.Returns(LockingState.Closed);

                var subsequent = handler.HandleRequestOption1().Result;

                // Verify behaviour
                Received.InOrder(() =>
                {
                    bikesViewModel.Received(1).IsIdle = false; // GUI must be locked
                    bikesViewModel.ActionText = "One moment please...";
                    pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action
                    bikesViewModel.ActionText = "Opening lock...";
                    locks.Received()[0].OpenAsync(); // Lock must be closed
                    bikesViewModel.ActionText = "";
                    viewService.DisplayAlert("Error while opening lock!", "After try to open lock state closed is reported.", "OK");
                    bikesViewModel.ActionText = "Updating...";
                    pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again
                    bikesViewModel.ActionText = "";
                    bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked
                });

                // Verify state "Booked Closed" after action
                Assert.AreEqual("Return bike", subsequent.ButtonText);
                Assert.IsTrue(subsequent.IsButtonVisible);
                Assert.AreEqual("Open lock & continue renting", subsequent.LockitButtonText);
                Assert.IsTrue(subsequent.IsLockitButtonVisible);
            }

            /// <summary>
            /// Use case: Open lock
            /// Final state: Same as initial state.
            /// </summary>
            [Test]
            public void TestOpenOpenFailsCouldntOpenInconsistentStateExecption()
            {
                var bike = Substitute.For<IBikeInfoMutable>();
                var connector = Substitute.For<IConnector>();
                var command = Substitute.For<ICommand>();
                var geolocation = Substitute.For<IGeolocation>();
                var locks = Substitute.For<ILocksService>();
                var pollingManager = Substitute.For<IPollingUpdateTaskManager>();
                var viewService = Substitute.For<IViewService>();
                var bikesViewModel = Substitute.For<IBikesViewModel>();
                var activeUser = Substitute.For<IUser>();

                var handler = new ReservedUnknown(
                    bike,
                    () => true, // isConnectedDelegate
                    (isConnexted) => connector,
                    geolocation,
                    locks,
                    () => pollingManager,
                    Substitute.For<ISmartDevice>(),
                    viewService,
                    bikesViewModel,
                    activeUser);

                locks[0].OpenAsync()
                    .Returns<Task<LockitLockingState?>>(x => throw new CouldntOpenBoldWasBlockedException());

                bike.State.Value.Returns(InUseStateEnum.Booked);
                bike.LockInfo.State.Returns(LockingState.Closed);

                var subsequent = handler.HandleRequestOption1().Result;

                // Verify behaviour
                Received.InOrder(() =>
                {
                    bikesViewModel.Received(1).IsIdle = false; // GUI must be locked
                    bikesViewModel.ActionText = "One moment please...";
                    pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action
                    bikesViewModel.ActionText = "Opening lock...";
                    locks.Received()[0].OpenAsync(); // Lock must be closed
                    bikesViewModel.ActionText = "";
                    viewService.DisplayAlert("Lock can not be opened!", "Lock was blocked and might still be. Please ensure that no obstacle prevents lock from opening and try again.", "OK");
                    bikesViewModel.ActionText = "Updating...";
                    pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again
                    bikesViewModel.ActionText = "";
                    bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked
                });

                // Verify state "Booked Disconnected" after action
                Assert.AreEqual("Open lock & continue renting", subsequent.ButtonText);
                Assert.IsTrue(subsequent.IsButtonVisible);
                Assert.AreEqual("Close lock", subsequent.LockitButtonText);
                Assert.IsTrue(subsequent.IsLockitButtonVisible);
            }

            /// <summary>
            /// Use case: Open lock
            /// Final state: Same as initial state.
            /// </summary>
            [Test]
            public void TestOpenOpenFailsException()
            {
                var bike = Substitute.For<IBikeInfoMutable>();
                var connector = Substitute.For<IConnector>();
                var command = Substitute.For<ICommand>();
                var geolocation = Substitute.For<IGeolocation>();
                var locks = Substitute.For<ILocksService>();
                var pollingManager = Substitute.For<IPollingUpdateTaskManager>();
                var viewService = Substitute.For<IViewService>();
                var bikesViewModel = Substitute.For<IBikesViewModel>();
                var activeUser = Substitute.For<IUser>();

                var handler = new ReservedUnknown(
                    bike,
                    () => true, // isConnectedDelegate
                    (isConnexted) => connector,
                    geolocation,
                    locks,
                    () => pollingManager,
                    Substitute.For<ISmartDevice>(),
                    viewService,
                    bikesViewModel,
                    activeUser);

                locks[0].OpenAsync()
                    .Returns<Task<LockitLockingState?>>(x => throw new Exception("Exception message."));

                bike.State.Value.Returns(InUseStateEnum.Booked);
                bike.LockInfo.State.Returns(LockingState.Closed);

                var subsequent = handler.HandleRequestOption1().Result;

                // Verify behaviour
                Received.InOrder(() =>
                {
                    bikesViewModel.Received(1).IsIdle = false; // GUI must be locked
                    bikesViewModel.ActionText = "One moment please...";
                    pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action
                    bikesViewModel.ActionText = "Opening lock...";
                    locks.Received()[0].OpenAsync(); // Lock must be closed
                    bikesViewModel.ActionText = "";
                    viewService.DisplayAlert("Error while opening lock!", "Exception message.", "OK");
                    bikesViewModel.ActionText = "Updating...";
                    pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again
                    bikesViewModel.ActionText = "";
                    bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked
                });

                // Verify state "Booked Disconnected" after action
                Assert.AreEqual(nameof(BookedDisconnected), subsequent.ButtonText);
                Assert.IsFalse(subsequent.IsButtonVisible);
                Assert.AreEqual("Search lock", subsequent.LockitButtonText);
                Assert.IsTrue(subsequent.IsLockitButtonVisible);
            }

            /// <summary>
            /// Use case: Open lock
            /// Final state: Booked open.
            /// </summary>
            [Test]
            public void TestOpenUpdateLockingStateFailsWebConnectFailureException()
            {
                var bike = Substitute.For<IBikeInfoMutable>();
                var connector = Substitute.For<IConnector>();
                var command = Substitute.For<ICommand>();
                var geolocation = Substitute.For<IGeolocation>();
                var locks = Substitute.For<ILocksService>();
                var pollingManager = Substitute.For<IPollingUpdateTaskManager>();
                var viewService = Substitute.For<IViewService>();
                var bikesViewModel = Substitute.For<IBikesViewModel>();
                var activeUser = Substitute.For<IUser>();

                var handler = new ReservedUnknown(
                    bike,
                    () => true, // isConnectedDelegate
                    (isConnexted) => connector,
                    geolocation,
                    locks,
                    () => pollingManager,
                    Substitute.For<ISmartDevice>(),
                    viewService,
                    bikesViewModel,
                    activeUser);

                locks[0].OpenAsync()
                    .Returns(LockitLockingState.Open);

                connector.Command.UpdateLockingStateAsync(bike, null).Returns(x => throw new WebConnectFailureException("Context info", new System.Exception("hoppla")));

                bike.State.Value.Returns(InUseStateEnum.Booked);
                bike.LockInfo.State.Returns(LockingState.Open);

                var subsequent = handler.HandleRequestOption1().Result;

                // Verify behaviour
                Received.InOrder(() =>
                {
                    bikesViewModel.Received(1).IsIdle = false; // GUI must be locked
                    bikesViewModel.ActionText = "One moment please...";
                    pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action
                    bikesViewModel.ActionText = "Opening lock...";
                    locks.Received()[0].OpenAsync(); // Lock must be closed
                    bikesViewModel.ActionText = "Reading charging level...";
                    locks[0].GetBatteryPercentageAsync();
                    bikesViewModel.ActionText = "Updating lock state...";
                    connector.Command.UpdateLockingStateAsync(bike, null);
                    bikesViewModel.ActionText = "No web error on updating locking status.";
                    bikesViewModel.ActionText = "Updating...";
                    pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again
                    bikesViewModel.ActionText = "";
                    bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked
                });

                // Verify state "Booked Closed" after action
                Assert.AreEqual("Close lock & return bike", subsequent.ButtonText);
                Assert.IsTrue(subsequent.IsButtonVisible);
                Assert.AreEqual("Close lock", subsequent.LockitButtonText);
                Assert.IsTrue(subsequent.IsLockitButtonVisible);
            }

            /// <summary>
            /// Use case: Open lock
            /// Final state: Booked open.
            /// </summary>
            [Test]
            public void TestOpenUpdateLockingStateFailsException()
            {
                var bike = Substitute.For<IBikeInfoMutable>();
                var connector = Substitute.For<IConnector>();
                var command = Substitute.For<ICommand>();
                var geolocation = Substitute.For<IGeolocation>();
                var locks = Substitute.For<ILocksService>();
                var pollingManager = Substitute.For<IPollingUpdateTaskManager>();
                var viewService = Substitute.For<IViewService>();
                var bikesViewModel = Substitute.For<IBikesViewModel>();
                var activeUser = Substitute.For<IUser>();

                var handler = new ReservedUnknown(
                    bike,
                    () => true, // isConnectedDelegate
                    (isConnexted) => connector,
                    geolocation,
                    locks,
                    () => pollingManager,
                    Substitute.For<ISmartDevice>(),
                    viewService,
                    bikesViewModel,
                    activeUser);

                locks[0].OpenAsync()
                    .Returns(LockitLockingState.Open);

                connector.Command.UpdateLockingStateAsync(bike, Arg.Any<LocationDto>()).Returns(x => throw new Exception("Exception message."));

                bike.State.Value.Returns(InUseStateEnum.Booked);
                bike.LockInfo.State.Returns(LockingState.Open);

                var subsequent = handler.HandleRequestOption1().Result;

                // Verify behaviour
                Received.InOrder(() =>
                {
                    bikesViewModel.Received(1).IsIdle = false; // GUI must be locked
                    bikesViewModel.ActionText = "One moment please...";
                    pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action
                    bikesViewModel.ActionText = "Opening lock...";
                    locks.Received()[0].OpenAsync(); // Lock must be closed
                    bikesViewModel.ActionText = "Reading charging level...";
                    locks[0].GetBatteryPercentageAsync();
                    bikesViewModel.ActionText = "Updating lock state...";
                    connector.Command.UpdateLockingStateAsync(bike, Arg.Any<LocationDto>());
                    bikesViewModel.ActionText = "Connection error on updating locking status.";
                    bikesViewModel.ActionText = "Updating...";
                    pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again
                    bikesViewModel.ActionText = "";
                    bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked
                });

                // Verify state "Booked Closed" after action
                Assert.AreEqual("Close lock & return bike", subsequent.ButtonText);
                Assert.IsTrue(subsequent.IsButtonVisible);
                Assert.AreEqual("Close lock", subsequent.LockitButtonText);
                Assert.IsTrue(subsequent.IsLockitButtonVisible);
            }

            /// <summary>
            /// Use case: Open lock
            /// Final state: Booked open.
            /// </summary>
            [Test]
            public void TestOpenUpdateLockingStateFailsResponseException()
            {
                var bike = Substitute.For<IBikeInfoMutable>();
                var connector = Substitute.For<IConnector>();
                var command = Substitute.For<ICommand>();
                var geolocation = Substitute.For<IGeolocation>();
                var locks = Substitute.For<ILocksService>();
                var pollingManager = Substitute.For<IPollingUpdateTaskManager>();
                var viewService = Substitute.For<IViewService>();
                var bikesViewModel = Substitute.For<IBikesViewModel>();
                var activeUser = Substitute.For<IUser>();

                var handler = new ReservedUnknown(
                    bike,
                    () => true, // isConnectedDelegate
                    (isConnexted) => connector,
                    geolocation,
                    locks,
                    () => pollingManager,
                    Substitute.For<ISmartDevice>(),
                    viewService,
                    bikesViewModel,
                    activeUser);

                locks[0].OpenAsync()
                    .Returns(LockitLockingState.Open);

                connector.Command.UpdateLockingStateAsync(bike, Arg.Any<LocationDto>()).Returns(x =>
                    throw new ReturnBikeException(JsonConvert.DeserializeObject<ReservationCancelReturnResponse>(@"{ ""response_text"" : ""Some invalid data received!""}"), "Outer message."));

                bike.State.Value.Returns(InUseStateEnum.Booked);
                bike.LockInfo.State.Returns(LockingState.Open);

                var subsequent = handler.HandleRequestOption1().Result;

                // Verify behaviour
                Received.InOrder(() =>
                {
                    bikesViewModel.Received(1).IsIdle = false; // GUI must be locked
                    bikesViewModel.ActionText = "One moment please...";
                    pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action
                    bikesViewModel.ActionText = "Opening lock...";
                    locks.Received()[0].OpenAsync(); // Lock must be closed
                    bikesViewModel.ActionText = "Reading charging level...";
                    locks[0].GetBatteryPercentageAsync();
                    bikesViewModel.ActionText = "Updating lock state...";
                    connector.Command.UpdateLockingStateAsync(bike, Arg.Any<LocationDto>());
                    bikesViewModel.ActionText = "Status error on updating lock state.";
                    bikesViewModel.ActionText = "Updating...";
                    pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again
                    bikesViewModel.ActionText = "";
                    bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked
                });

                // Verify state "Booked Closed" after action
                Assert.AreEqual("Close lock & return bike", subsequent.ButtonText);
                Assert.IsTrue(subsequent.IsButtonVisible);
                Assert.AreEqual("Close lock", subsequent.LockitButtonText);
                Assert.IsTrue(subsequent.IsLockitButtonVisible);
            }

            /// <summary>
            /// Use case: Close lock
            /// Final state: Booked closed.
            /// </summary>
            [Test]
            public void TestClose()
            {
                var bike = Substitute.For<IBikeInfoMutable>();
                var connector = Substitute.For<IConnector>();
                var command = Substitute.For<ICommand>();
                var geolocation = Substitute.For<IGeolocation>();
                var locks = Substitute.For<ILocksService>();
                var pollingManager = Substitute.For<IPollingUpdateTaskManager>();
                var viewService = Substitute.For<IViewService>();
                var bikesViewModel = Substitute.For<IBikesViewModel>();
                var activeUser = Substitute.For<IUser>();

                var handler = new ReservedUnknown(
                    bike,
                    () => true, // isConnectedDelegate
                    (isConnexted) => connector,
                    geolocation,
                    locks,
                    () => pollingManager,
                    Substitute.For<ISmartDevice>(),
                    viewService,
                    bikesViewModel,
                    activeUser);

                locks[0].CloseAsync()
                    .Returns(Task.FromResult((LockitLockingState?)LockitLockingState.Closed)); // Return lock state indicating success

                bike.State.Value.Returns(InUseStateEnum.Booked);

                var subsequent = handler.HandleRequestOption2().Result;

                // Verify behaviour
                Received.InOrder(() =>
                {
                    bikesViewModel.Received(1).IsIdle = false; // GUI must be locked
                    bikesViewModel.ActionText = "One moment please...";
                    pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action
                    bikesViewModel.ActionText = "Closing lock...";
                    locks.Received()[0].CloseAsync(); // Lock must be closed
                    bikesViewModel.ActionText = "Updating lock state...";
                    connector.Command.UpdateLockingStateAsync(bike, Arg.Any<LocationDto>());
                    bikesViewModel.ActionText = "Updating...";
                    pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again
                    bikesViewModel.ActionText = "";
                    bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked
                });

                // Verify state "Booked Closed" after action
                Assert.AreEqual("Return bike", subsequent.ButtonText);
                Assert.IsTrue(subsequent.IsButtonVisible);
                Assert.AreEqual("Open lock & continue renting", subsequent.LockitButtonText);
                Assert.IsTrue(subsequent.IsLockitButtonVisible);
            }

            [Test]
            public void TestCloseCloseFailsOutOfReachException()
            {
                var bike = Substitute.For<IBikeInfoMutable>();
                var connector = Substitute.For<IConnector>();
                var command = Substitute.For<ICommand>();
                var geolocation = Substitute.For<IGeolocation>();
                var locks = Substitute.For<ILocksService>();
                var pollingManager = Substitute.For<IPollingUpdateTaskManager>();
                var viewService = Substitute.For<IViewService>();
                var bikesViewModel = Substitute.For<IBikesViewModel>();
                var activeUser = Substitute.For<IUser>();

                var handler = new ReservedUnknown(
                    bike,
                    () => true, // isConnectedDelegate
                    (isConnexted) => connector,
                    geolocation,
                    locks,
                    () => pollingManager,
                    Substitute.For<ISmartDevice>(),
                    viewService,
                    bikesViewModel,
                    activeUser);

                locks[0].CloseAsync()
                    .Returns<Task<LockitLockingState?>>(x => throw new OutOfReachException());

                bike.State.Value.Returns(InUseStateEnum.Booked);
                bike.LockInfo.State.Returns(LockingState.Open);

                var subsequent = handler.HandleRequestOption2().Result;

                // Verify behaviour
                Received.InOrder(() =>
                {
                    bikesViewModel.Received(1).IsIdle = false; // GUI must be locked
                    bikesViewModel.ActionText = "One moment please...";
                    pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action
                    bikesViewModel.ActionText = "Closing lock...";
                    locks.Received()[0].CloseAsync(); // Lock must be closed
                    bikesViewModel.ActionText = "";
                    viewService.DisplayAlert("Lock can not be closed!", "Lock cannot be closed until bike is near.", "OK");
                    bikesViewModel.ActionText = "Updating...";
                    pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again
                    bikesViewModel.ActionText = "";
                    bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked
                });

                // Verify state "Booked disconnected" after action
                Assert.AreEqual("BookedDisconnected", subsequent.ButtonText);
                Assert.IsFalse(subsequent.IsButtonVisible);
                Assert.AreEqual("Search lock", subsequent.LockitButtonText);
                Assert.IsTrue(subsequent.IsLockitButtonVisible);
            }

            /// <summary>
            /// Use case: Close lock
            /// Final state: Same as initial state.
            /// </summary>
            [Test]
            public void TestCloseCloseFailsException()
            {
                var bike = Substitute.For<IBikeInfoMutable>();
                var connector = Substitute.For<IConnector>();
                var command = Substitute.For<ICommand>();
                var geolocation = Substitute.For<IGeolocation>();
                var locks = Substitute.For<ILocksService>();
                var pollingManager = Substitute.For<IPollingUpdateTaskManager>();
                var viewService = Substitute.For<IViewService>();
                var bikesViewModel = Substitute.For<IBikesViewModel>();
                var activeUser = Substitute.For<IUser>();

                var handler = new ReservedUnknown(
                    bike,
                    () => true, // isConnectedDelegate
                    (isConnexted) => connector,
                    geolocation,
                    locks,
                    () => pollingManager,
                    Substitute.For<ISmartDevice>(),
                    viewService,
                    bikesViewModel,
                    activeUser);

                locks[0].CloseAsync()
                    .Returns<Task<LockitLockingState?>>(x => throw new Exception("Exception message."));

                bike.State.Value.Returns(InUseStateEnum.Booked);
                bike.LockInfo.State.Returns(LockingState.Open);

                var subsequent = handler.HandleRequestOption2().Result;

                // Verify behaviour
                Received.InOrder(() =>
                {
                    bikesViewModel.Received(1).IsIdle = false; // GUI must be locked
                    bikesViewModel.ActionText = "One moment please...";
                    pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action
                    bikesViewModel.ActionText = "Closing lock...";
                    locks.Received()[0].CloseAsync(); // Lock must be closed
                    bikesViewModel.ActionText = "";
                    viewService.DisplayAlert("Lock can not be closed!", "Exception message.", "OK");
                    bikesViewModel.ActionText = "Updating...";
                    pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again
                    bikesViewModel.ActionText = "";
                    bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked
                });

                // Verify state "Booked Disconnected" after action
                Assert.AreEqual("BookedDisconnected", subsequent.ButtonText);
                Assert.IsFalse(subsequent.IsButtonVisible);
                Assert.AreEqual("Search lock", subsequent.LockitButtonText);
                Assert.IsTrue(subsequent.IsLockitButtonVisible);
            }
        }

        /// <summary>
        /// Use case: Close lock
        /// Final state: Booked closed.
        /// </summary>
        [Test]
        public void TestCloseUpdateLockingStateFailsWebConnectFailureException()
        {
            var bike = Substitute.For<IBikeInfoMutable>();
            var connector = Substitute.For<IConnector>();
            var command = Substitute.For<ICommand>();
            var geolocation = Substitute.For<IGeolocation>();
            var locks = Substitute.For<ILocksService>();
            var pollingManager = Substitute.For<IPollingUpdateTaskManager>();
            var viewService = Substitute.For<IViewService>();
            var bikesViewModel = Substitute.For<IBikesViewModel>();
            var activeUser = Substitute.For<IUser>();

            var handler = new ReservedUnknown(
                bike,
                () => true, // isConnectedDelegate
                (isConnexted) => connector,
                geolocation,
                locks,
                () => pollingManager,
                Substitute.For<ISmartDevice>(),
                viewService,
                bikesViewModel,
                activeUser);

            locks[0].CloseAsync()
                .Returns(LockitLockingState.Closed); // Return lock state indicating success

            connector.Command.UpdateLockingStateAsync(bike, Arg.Any<LocationDto>()).Returns(x => throw new WebConnectFailureException("Context info", new System.Exception("hoppla")));

            bike.State.Value.Returns(InUseStateEnum.Booked);

            var subsequent = handler.HandleRequestOption2().Result;

            // Verify behaviour
            Received.InOrder(() =>
            {
                bikesViewModel.Received(1).IsIdle = false; // GUI must be locked
                bikesViewModel.ActionText = "One moment please...";
                pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action
                bikesViewModel.ActionText = "Closing lock...";
                locks.Received()[0].CloseAsync(); // Lock must be closed
                bikesViewModel.ActionText = "Updating lock state...";
                connector.Command.UpdateLockingStateAsync(bike, Arg.Any<LocationDto>());
                bikesViewModel.ActionText = "No web error on updating locking status.";
                bikesViewModel.ActionText = "Updating...";
                pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again
                bikesViewModel.ActionText = "";
                bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked
            });

            // Verify state "Booked Closed" after action
            Assert.AreEqual("Return bike", subsequent.ButtonText);
            Assert.IsTrue(subsequent.IsButtonVisible);
            Assert.AreEqual("Open lock & continue renting", subsequent.LockitButtonText);
            Assert.IsTrue(subsequent.IsLockitButtonVisible);
        }

        /// <summary>
        /// Use case: close lock
        /// Final state: Booked closed.
        /// </summary>
        [Test]
        public void TestCloseUpdateLockingStateFailsException()
        {
            var bike = Substitute.For<IBikeInfoMutable>();
            var connector = Substitute.For<IConnector>();
            var command = Substitute.For<ICommand>();
            var geolocation = Substitute.For<IGeolocation>();
            var locks = Substitute.For<ILocksService>();
            var pollingManager = Substitute.For<IPollingUpdateTaskManager>();
            var viewService = Substitute.For<IViewService>();
            var bikesViewModel = Substitute.For<IBikesViewModel>();
            var activeUser = Substitute.For<IUser>();

            var handler = new ReservedUnknown(
                bike,
                () => true, // isConnectedDelegate
                (isConnexted) => connector,
                geolocation,
                locks,
                () => pollingManager,
                Substitute.For<ISmartDevice>(),
                viewService,
                bikesViewModel,
                activeUser);

            locks[0].CloseAsync()
                .Returns(LockitLockingState.Closed); // Return lock state indicating success

            connector.Command.UpdateLockingStateAsync(bike, Arg.Any<LocationDto>()).Returns(x => throw new Exception("Exception message."));

            bike.State.Value.Returns(InUseStateEnum.Booked);

            var subsequent = handler.HandleRequestOption2().Result;

            // Verify behaviour
            Received.InOrder(() =>
            {
                bikesViewModel.Received(1).IsIdle = false; // GUI must be locked
                bikesViewModel.ActionText = "One moment please...";
                pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action
                bikesViewModel.ActionText = "Closing lock...";
                locks.Received()[0].CloseAsync(); // Lock must be closed
                bikesViewModel.ActionText = "Updating lock state...";
                connector.Command.UpdateLockingStateAsync(bike, Arg.Any<LocationDto>());
                bikesViewModel.ActionText = "Connection error on updating locking status.";
                bikesViewModel.ActionText = "Updating...";
                pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again
                bikesViewModel.ActionText = "";
                bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked
            });

            // Verify state "Booked Closed" after action
            Assert.AreEqual("Return bike", subsequent.ButtonText);
            Assert.IsTrue(subsequent.IsButtonVisible);
            Assert.AreEqual("Open lock & continue renting", subsequent.LockitButtonText);
            Assert.IsTrue(subsequent.IsLockitButtonVisible);
        }

        /// <summary>
        /// Use case: close lock
        /// Final state: Booked closed.
        /// </summary>
        [Test]
        public void TestCloseUpdateLockingStateFailsResponseException()
        {
            var bike = Substitute.For<IBikeInfoMutable>();
            var connector = Substitute.For<IConnector>();
            var command = Substitute.For<ICommand>();
            var geolocation = Substitute.For<IGeolocation>();
            var locks = Substitute.For<ILocksService>();
            var pollingManager = Substitute.For<IPollingUpdateTaskManager>();
            var viewService = Substitute.For<IViewService>();
            var bikesViewModel = Substitute.For<IBikesViewModel>();
            var activeUser = Substitute.For<IUser>();

            var handler = new ReservedUnknown(
                bike,
                () => true, // isConnectedDelegate
                (isConnexted) => connector,
                geolocation,
                locks,
                () => pollingManager,
                Substitute.For<ISmartDevice>(),
                viewService,
                bikesViewModel,
                activeUser);

            locks[0].CloseAsync()
                .Returns(LockitLockingState.Closed); // Return lock state indicating success

            connector.Command.UpdateLockingStateAsync(bike, Arg.Any<LocationDto>()).Returns(x =>
                throw new ReturnBikeException(JsonConvert.DeserializeObject<ReservationCancelReturnResponse>(@"{ ""response_text"" : ""Some invalid data received!""}"), "Outer message."));

            bike.State.Value.Returns(InUseStateEnum.Booked);

            var subsequent = handler.HandleRequestOption2().Result;

            // Verify behaviour
            Received.InOrder(() =>
            {
                bikesViewModel.Received(1).IsIdle = false; // GUI must be locked
                bikesViewModel.ActionText = "One moment please...";
                pollingManager.StopUpdatePeridically(); // Polling must be stopped before any COPR and lock service action
                bikesViewModel.ActionText = "Closing lock...";
                locks.Received()[0].CloseAsync(); // Lock must be closed
                bikesViewModel.ActionText = "Updating lock state...";
                connector.Command.UpdateLockingStateAsync(bike, Arg.Any<LocationDto>());
                bikesViewModel.ActionText = "Status error on updating lock state.";
                bikesViewModel.ActionText = "Updating...";
                pollingManager.StartUpdateAyncPeridically(); // polling must be restarted again
                bikesViewModel.ActionText = "";
                bikesViewModel.Received(1).IsIdle = true; // GUI must be unlocked
            });

            // Verify state "Booked Closed" after action
            Assert.AreEqual("Return bike", subsequent.ButtonText);
            Assert.IsTrue(subsequent.IsButtonVisible);
            Assert.AreEqual("Open lock & continue renting", subsequent.LockitButtonText);
            Assert.IsTrue(subsequent.IsLockitButtonVisible);
        }
    }
}