using System;
using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.App.Models;
using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Models.Domain;
using Xamarin.Forms;

namespace Bit.App.Utilities.AccountManagement
{
    public static class AccountsManagerMessageCommands
    {
        public const string LOCKED = "locked";
        public const string LOCK_VAULT = "lockVault";
        public const string LOGOUT = "logout";
        public const string LOGGED_OUT = "loggedOut";
        public const string ADD_ACCOUNT = "addAccount";
        public const string ACCOUNT_ADDED = "accountAdded";
        public const string SWITCHED_ACCOUNT = "switchedAccount";
    }

    public class AccountsManager : IAccountsManager
    {
        private readonly IBroadcasterService _broadcasterService;
        private readonly IVaultTimeoutService _vaultTimeoutService;
        private readonly IStorageService _secureStorageService;
        private readonly IStateService _stateService;
        private readonly IPlatformUtilsService _platformUtilsService;
        private readonly IAuthService _authService;

        Func<AppOptions> _getOptionsFunc;
        private IAccountsManagerHost _accountsManagerHost;

        public AccountsManager(IBroadcasterService broadcasterService,
                               IVaultTimeoutService vaultTimeoutService,
                               IStorageService secureStorageService,
                               IStateService stateService,
                               IPlatformUtilsService platformUtilsService,
                               IAuthService authService)
        {
            _broadcasterService = broadcasterService;
            _vaultTimeoutService = vaultTimeoutService;
            _secureStorageService = secureStorageService;
            _stateService = stateService;
            _platformUtilsService = platformUtilsService;
            _authService = authService;
        }

        private AppOptions Options => _getOptionsFunc?.Invoke() ?? new AppOptions { IosExtension = true };

        public void Init(Func<AppOptions> getOptionsFunc, IAccountsManagerHost accountsManagerHost)
        {
            _getOptionsFunc = getOptionsFunc;
            _accountsManagerHost = accountsManagerHost;

            _broadcasterService.Subscribe(nameof(AccountsManager), OnMessage);
        }

        public async Task NavigateOnAccountChangeAsync(bool? isAuthed = null)
        {
            // TODO: this could be improved by doing chain of responsability pattern
            // but for now it may be an overkill, if logic gets more complex consider refactoring it

            var authed = isAuthed ?? await _stateService.IsAuthenticatedAsync();
            if (authed)
            {
                if (await _vaultTimeoutService.IsLoggedOutByTimeoutAsync() ||
                    await _vaultTimeoutService.ShouldLogOutByTimeoutAsync())
                {
                    // TODO implement orgIdentifier flow to SSO Login page, same as email flow below
                    // var orgIdentifier = await _stateService.GetOrgIdentifierAsync();

                    var email = await _stateService.GetEmailAsync();
                    Options.HideAccountSwitcher = await _stateService.GetActiveUserIdAsync() == null;
                    _accountsManagerHost.Navigate(NavigationTarget.Login, new LoginNavigationParams(email));
                }
                else if (await _vaultTimeoutService.IsLockedAsync() ||
                         await _vaultTimeoutService.ShouldLockAsync())
                {
                    _accountsManagerHost.Navigate(NavigationTarget.Lock);
                }
                else if (Options.FromAutofillFramework && Options.SaveType.HasValue)
                {
                    _accountsManagerHost.Navigate(NavigationTarget.AddEditCipher);
                }
                else if (Options.Uri != null)
                {
                    _accountsManagerHost.Navigate(NavigationTarget.AutofillCiphers);
                }
                else if (Options.CreateSend != null)
                {
                    _accountsManagerHost.Navigate(NavigationTarget.SendAddEdit);
                }
                else
                {
                    _accountsManagerHost.Navigate(NavigationTarget.Home);
                }
            }
            else
            {
                Options.HideAccountSwitcher = await _stateService.GetActiveUserIdAsync() == null;
                if (await _vaultTimeoutService.IsLoggedOutByTimeoutAsync() ||
                    await _vaultTimeoutService.ShouldLogOutByTimeoutAsync())
                {
                    // TODO implement orgIdentifier flow to SSO Login page, same as email flow below
                    // var orgIdentifier = await _stateService.GetOrgIdentifierAsync();

                    var email = await _stateService.GetEmailAsync();
                    _accountsManagerHost.Navigate(NavigationTarget.Login, new LoginNavigationParams(email));
                }
                else
                {
                    _accountsManagerHost.Navigate(NavigationTarget.HomeLogin);
                }
            }
        }

        private async void OnMessage(Message message)
        {
            switch (message.Command)
            {
                case AccountsManagerMessageCommands.LOCKED:
                    Locked(message.Data as Tuple<string, bool>);
                    break;
                case AccountsManagerMessageCommands.LOCK_VAULT:
                    await _vaultTimeoutService.LockAsync(true);
                    break;
                case AccountsManagerMessageCommands.LOGOUT:
                    LogOut(message.Data as Tuple<string, bool, bool>);
                    break;
                case AccountsManagerMessageCommands.LOGGED_OUT:
                    // Clean up old migrated key if they ever log out.
                    await _secureStorageService.RemoveAsync("oldKey");
                    break;
                case AccountsManagerMessageCommands.ADD_ACCOUNT:
                    AddAccount();
                    break;
                case AccountsManagerMessageCommands.ACCOUNT_ADDED:
                    await _accountsManagerHost.UpdateThemeAsync();
                    break;
                case AccountsManagerMessageCommands.SWITCHED_ACCOUNT:
                    await SwitchedAccountAsync();
                    break;
            }
        }

        private void Locked(Tuple<string, bool> extras)
        {
            var userId = extras?.Item1;
            var userInitiated = extras?.Item2 ?? false;
            Device.BeginInvokeOnMainThread(async () => await LockedAsync(userId, userInitiated));
        }

        private async Task LockedAsync(string userId, bool userInitiated)
        {
            if (!await _stateService.IsActiveAccountAsync(userId))
            {
                _platformUtilsService.ShowToast("info", null, AppResources.AccountLockedSuccessfully);
                return;
            }

            var autoPromptBiometric = !userInitiated;
            if (autoPromptBiometric && Device.RuntimePlatform == Device.iOS)
            {
                var vaultTimeout = await _stateService.GetVaultTimeoutAsync();
                if (vaultTimeout == 0)
                {
                    autoPromptBiometric = false;
                }
            }

            await _accountsManagerHost.SetPreviousPageInfoAsync();

            Device.BeginInvokeOnMainThread(() => _accountsManagerHost.Navigate(NavigationTarget.Lock, new LockNavigationParams(autoPromptBiometric)));
        }

        private void AddAccount()
        {
            Device.BeginInvokeOnMainThread(() =>
            {
                Options.HideAccountSwitcher = false;
                _accountsManagerHost.Navigate(NavigationTarget.HomeLogin);
            });
        }

        private void LogOut(Tuple<string, bool, bool> extras)
        {
            var userId = extras?.Item1;
            var userInitiated = extras?.Item2 ?? true;
            var expired = extras?.Item3 ?? false;
            Device.BeginInvokeOnMainThread(async () => await LogOutAsync(userId, userInitiated, expired));
        }

        private async Task LogOutAsync(string userId, bool userInitiated, bool expired)
        {
            await AppHelpers.LogOutAsync(userId, userInitiated);
            await NavigateOnAccountChangeAsync();
            _authService.LogOut(() =>
            {
                if (expired)
                {
                    _platformUtilsService.ShowToast("warning", null, AppResources.LoginExpired);
                }
            });
        }

        private async Task SwitchedAccountAsync()
        {
            await AppHelpers.OnAccountSwitchAsync();
            Device.BeginInvokeOnMainThread(async () =>
            {
                if (await _vaultTimeoutService.ShouldTimeoutAsync())
                {
                    await _vaultTimeoutService.ExecuteTimeoutActionAsync();
                }
                else
                {
                    await NavigateOnAccountChangeAsync();
                }
                await Task.Delay(50);
                await _accountsManagerHost.UpdateThemeAsync();
            });
        }
    }
}
