using Serilog;
using System;
using System.Threading.Tasks;
using TINK.Model.Bike.BluetoothLock;
using TINK.Model.Connector;
using TINK.Model.State;
using TINK.View;
using TINK.Repository.Exception;
using TINK.Model.Services.Geolocation;
using TINK.Services.BluetoothLock;
using TINK.Services.BluetoothLock.Exception;
using TINK.MultilingualResources;
using TINK.Model.Bikes.Bike.BluetoothLock;
using TINK.Model.User;
using TINK.Model.Device;

namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler
{
    /// <summary> Bike is disposable, lock is open and connected to app. </summary>
    /// <remarks> 
    /// This state can not be occur because
    /// - app does not allow to return bike/ cancel reservation when lock is not closed
    /// - as long as app is connected to lock
    ///     - lock can not be opened manually
    ///     - no other device can access lock
    /// </remarks>
    public class DisposableOpen : Base, IRequestHandler
    {
        /// <summary> Bike is disposable, lock is open and can be reached via bluetooth. </summary>
        /// <remarks> 
        /// This state should never occure because as long as a ILOCKIT is connected it
        /// - cannot be closed manually 
        /// - no other device can access lock
        /// - app itself should never event attempt to open a lock which is not rented.
        /// </remarks>
        /// <param name="smartDevice">Provides info about the smart device (phone, tablet, ...)</param>
        /// <param name="bikesViewModel">View model to be used for progress report and unlocking/ locking view.</param>
        public DisposableOpen(
               IBikeInfoMutable selectedBike,
               Func<bool> isConnectedDelegate,
               Func<bool, IConnector> connectorFactory,
               IGeolocation geolocation,
               ILocksService lockService,
               Func<IPollingUpdateTaskManager> viewUpdateManager,
               ISmartDevice smartDevice,
               IViewService viewService,
               IBikesViewModel bikesViewModel,
               IUser activeUser) : base(
                   selectedBike,
                   AppResources.ActionBookOrClose,
                   true, // Show copri button to enable reserving
                   isConnectedDelegate,
                   connectorFactory,
                   geolocation,
                   lockService,
                   viewUpdateManager,
                   smartDevice,
                   viewService,
                   bikesViewModel,
                   activeUser)
            {
                LockitButtonText = GetType().Name;
                IsLockitButtonVisible = false; 
            }

        /// <summary> Gets the bike state. </summary>
        public override InUseStateEnum State => InUseStateEnum.Disposable;

        /// <summary>Books bike by reserving bike, opening lock and booking bike.</summary>
        /// <returns>Next request handler.</returns>
        public async Task<IRequestHandler> HandleRequestOption1() => await DoBookOrClose();

        public async Task<IRequestHandler> HandleRequestOption2() => await UnsupportedRequest();


        /// <summary>Books bike by reserving bike, opening lock and booking bike.</summary>
        /// <returns>Next request handler.</returns>
        public async Task<IRequestHandler> DoBookOrClose()
        {
            BikesViewModel.IsIdle = false; // Lock list to avoid multiple taps while copri action is pending.

            // Stop polling before requesting bike.
            BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease;
            await ViewUpdateManager().StopUpdatePeridically();

            // Ask whether to really book bike or close lock?
            var l_oResult = await ViewService.DisplayAlert(
                string.Empty,
                $"Fahrrad {SelectedBike.GetFullDisplayName()} mieten oder Schloss schließen?",
                "Mieten",
                "Schloss schließen");

            if (l_oResult == false)
            {
                // Close lock
                Log.ForContext<DisposableOpen>().Information("User selected disposable bike {bike} in order to close lock.", SelectedBike);

                // Unlock bike.
                BikesViewModel.ActionText = AppResources.ActivityTextClosingLock;
                try
                {
                    SelectedBike.LockInfo.State = (await LockService[SelectedBike.LockInfo.Id].CloseAsync())?.GetLockingState() ?? LockingState.Disconnected;
                }
                catch (Exception exception)
                {
                    BikesViewModel.ActionText = string.Empty;
                    if (exception is OutOfReachException)
                    {
                        Log.ForContext<DisposableOpen>().Debug("Lock can not be closed. {Exception}", exception);

                        await ViewService.DisplayAlert(
                            AppResources.ErrorCloseLockTitle,
                            AppResources.ErrorCloseLockOutOfReachMessage,
                            "OK");
                    }
                    else if (exception is CounldntCloseMovingException)
                    {
                        Log.ForContext<BookedOpen>().Debug("Lock can not be closed. Lock is out of reach. {Exception}", exception);

                        await ViewService.DisplayAlert(
                            AppResources.ErrorCloseLockTitle,
                            AppResources.ErrorCloseLockMovingMessage,
                            "OK");
                    }
                    else if (exception is CouldntCloseBoldBlockedException)
                    {
                        Log.ForContext<BookedOpen>().Debug("Lock can not be closed. Lock is out of reach. {Exception}", exception);

                        await ViewService.DisplayAlert(
                            AppResources.ErrorCloseLockTitle,
                            AppResources.ErrorCloseLockBoldBlockedMessage,
                            "OK");
                    }
                    else
                    {
                        Log.ForContext<DisposableOpen>().Error("Lock can not be closed. {Exception}", exception);

                        await ViewService.DisplayAlert(
                            AppResources.ErrorCloseLockTitle,
                            exception.Message,
                            "OK");
                    }

                    SelectedBike.LockInfo.State = exception is StateAwareException stateAwareException
                        ? stateAwareException.State
                        : LockingState.Disconnected;

                    BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
                    await ViewUpdateManager().StartUpdateAyncPeridically();
                    BikesViewModel.ActionText = string.Empty;
                    BikesViewModel.IsIdle = true;
                    return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
                }

                // Disconnect lock.
                BikesViewModel.ActionText = AppResources.ActivityTextDisconnectingLock;
                try
                {
                    SelectedBike.LockInfo.State = await LockService.DisconnectAsync(SelectedBike.LockInfo.Id, SelectedBike.LockInfo.Guid);
                }
                catch (Exception exception)
                {
                    Log.ForContext<ReservedClosed>().Error("Lock can not be disconnected. {Exception}", exception);

                    BikesViewModel.ActionText = AppResources.ActivityTextErrorDisconnect;
                }

                BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
                await ViewUpdateManager().StartUpdateAyncPeridically();
                BikesViewModel.ActionText = string.Empty;
                BikesViewModel.IsIdle = true;
                return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
            }

            // Lock list to avoid multiple taps while copri action is pending.
            Log.ForContext<DisposableOpen>().Information("Request to book bike {bike}.", SelectedBike);

            BikesViewModel.ActionText = AppResources.ActivityTextReadingChargingLevel;
            try
            {
                SelectedBike.LockInfo.BatteryPercentage = await LockService[SelectedBike.LockInfo.Id].GetBatteryPercentageAsync();
            }
            catch (Exception exception)
            {
                if (exception is OutOfReachException)
                {
                    Log.ForContext<DisposableOpen>().Debug("Akkustate can not be read, bike out of range. {Exception}", exception);

                    BikesViewModel.ActionText = AppResources.ActivityTextErrorReadingChargingLevelOutOfReach;
                }
                else
                {
                    Log.ForContext<DisposableOpen>().Error("Akkustate can not be read. {Exception}", exception);

                    BikesViewModel.ActionText = AppResources.ActivityTextErrorReadingChargingLevelGeneral;
                }
            }

            // Notify corpi about unlock action in order to start booking.
            BikesViewModel.ActionText = AppResources.ActivityTextRentingBike;
            try
            {
                await ConnectorFactory(IsConnected).Command.DoBook(SelectedBike);
            }
            catch (Exception l_oException)
            {
                BikesViewModel.ActionText = string.Empty;

                if (l_oException is WebConnectFailureException)
                {
                    // Copri server is not reachable.
                    Log.ForContext<DisposableOpen>().Information("User selected requested bike {l_oId} but reserving failed (Copri server not reachable).", SelectedBike.Id);

                    await ViewService.DisplayAlert(
                        AppResources.MessageRentingBikeErrorConnectionTitle,
                        string.Format(AppResources.MessageErrorLockIsClosedThreeLines, l_oException.Message, WebConnectFailureException.GetHintToPossibleExceptionsReasons),
                        AppResources.MessageAnswerOk);
                }
                else
                {
                    Log.ForContext<DisposableOpen>().Error("User selected requested bike {l_oId} but reserving failed. {@l_oException}", SelectedBike.Id, l_oException);

                    await ViewService.DisplayAlert(
                        AppResources.MessageRentingBikeErrorGeneralTitle, 
                        string.Format(AppResources.MessageErrorLockIsClosedTwoLines, l_oException.Message), 
                        AppResources.MessageAnswerOk);
                }

                // If booking failed lock bike again because bike is only reserved.
                BikesViewModel.ActionText = "Verschließe Schloss...";
                try
                {
                    SelectedBike.LockInfo.State = (await LockService[SelectedBike.LockInfo.Id].CloseAsync())?.GetLockingState() ?? LockingState.Disconnected;
                }
                catch (Exception exception)
                {
                    Log.ForContext<DisposableOpen>().Error("Locking bike after booking failure failed. {Exception}", exception);

                    SelectedBike.LockInfo.State = exception is StateAwareException stateAwareException
                        ? stateAwareException.State
                        : LockingState.Disconnected;
                }

                // Disconnect lock.
                BikesViewModel.ActionText = AppResources.ActivityTextDisconnectingLock;
                try
                {
                    SelectedBike.LockInfo.State = await LockService.DisconnectAsync(SelectedBike.LockInfo.Id, SelectedBike.LockInfo.Guid);
                }
                catch (Exception exception)
                {
                    Log.ForContext<ReservedClosed>().Error("Lock can not be disconnected. {Exception}", exception);

                    BikesViewModel.ActionText = AppResources.ActivityTextErrorDisconnect;
                }

                // Restart polling again.
                BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
                await ViewUpdateManager().StartUpdateAyncPeridically();

                // Update status text and unlock list of bikes because no more action is pending.
                BikesViewModel.ActionText = string.Empty; // Todo: Move this statement in front of finally block because in catch block BikesViewModel.ActionText is already set to empty.
                BikesViewModel.IsIdle = true;
                return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
            }

            Log.ForContext<DisposableOpen>().Information("User reserved bike {bike} successfully.", SelectedBike);

            // Restart polling again.
            BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater;
            await ViewUpdateManager().StartUpdateAyncPeridically();

            // Update status text and unlock list of bikes because no more action is pending.
            BikesViewModel.ActionText = string.Empty; // Todo: Move this statement in front of finally block because in catch block BikesViewModel.ActionText is already set to empty.
            BikesViewModel.IsIdle = true;
            return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, Geolocation, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser);
        }

        /// <summary> Requst is not supported, button should be disabled. </summary>
        /// <returns></returns>
        public async Task<IRequestHandler> UnsupportedRequest()
        {
            Log.ForContext<DisposableOpen>().Error("Click of unsupported button click detected.");
            return await Task.FromResult<IRequestHandler>(this);
        }
    }
}