using Serilog;
using Serilog.Events;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Threading.Tasks;
using TINK.Model;
using TINK.Model.Connector;
using TINK.Repository.Exception;
using TINK.Model.Services.Geolocation;
using TINK.Settings;
using TINK.View;
using TINK.ViewModel.Map;
using TINK.ViewModel.Settings;
using System.Linq;
using TINK.Model.User.Account;
using TINK.Services.BluetoothLock;
using Xamarin.Forms;
using TINK.Services;

namespace TINK.ViewModel
{
    /// <summary>
    /// View model for settings.
    /// </summary>
    public class SettingsPageViewModel : INotifyPropertyChanged
    {
        /// <summary>
        /// Reference on view servcie to show modal notifications and to perform navigation.
        /// </summary>
        private IViewService m_oViewService;

        /// <summary>
        /// Fired if a property changes.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary> Object to manage update of view model objects from Copri.</summary>
        private IPollingUpdateTaskManager m_oViewUpdateManager;

        /// <summary> List of copri server uris.</summary>
        public CopriServerUriListViewModel CopriServerUriList { get; }

        /// <summary> Manages selection of locks services.</summary>
        public LocksServicesViewModel LocksServices { get; }

        /// <summary> Manages selection of geolocation services.</summary>
        public ServicesViewModel GeolocationServices { get; }

        /// <summary>
        /// Object to switch logging level.
        /// </summary>
        private LogEventLevel m_oMinimumLogEventLevel;

        /// <summary> Gets a value indicating whether reporting level is verbose or not.</summary>
        public bool IsReportLevelVerbose { get; set; }

        /// <summary> List of copri server uris.</summary>
        public ServicesViewModel Themes { get; }

        /// <summary> Reference on the tink app instance. </summary>
        private ITinkApp TinkApp { get; }

        IServicesContainer<IGeolocation> GeoloctionServicesContainer { get; }

        /// <summary> Constructs a settings page view model object.</summary>
        /// <param name="tinkApp"> Reference to tink app model.</param>
        /// <param name="p_oUser"></param>
        /// <param name="p_oDevice"></param>
        /// <param name="p_oFilterGroup">Filter to apply on stations and bikes.</param>
        /// <param name="p_oUris">Available copri server host uris including uri to use for next start.</param>
        /// <param name="p_oPolling"> Holds whether to poll or not and the periode leght is polling is on. </param>
        /// <param name="p_oDefaultPollingPeriode">Default polling periode lenght.</param>
        /// <param name="p_oMinimumLogEventLevel">Controls logging level.</param>
        /// <param name="p_oViewService">Interface to view</param>
        public SettingsPageViewModel(
            ITinkApp tinkApp,
            IServicesContainer<IGeolocation> geoloctionServicesContainer,
            IViewService p_oViewService)
        {
            TinkApp = tinkApp
                ?? throw new ArgumentException("Can not instantiate settings page view model- object. No tink app object available.");

            GeoloctionServicesContainer = geoloctionServicesContainer
                ?? throw new ArgumentException($"Can not instantiate {nameof(SettingsPageViewModel)}- object. Geolocation services container object must not be null.");

            m_oViewService = p_oViewService
                ?? throw new ArgumentException("Can not instantiate settings page view model- object. No user view service available.");

            m_oMinimumLogEventLevel = TinkApp.MinimumLogEventLevel;

            IsReportLevelVerbose = TinkApp.IsReportLevelVerbose;

            CenterMapToCurrentLocation = TinkApp.CenterMapToCurrentLocation;

            ExternalFolder = TinkApp.ExternalFolder;

            IsLogToExternalFolderVisible = !string.IsNullOrEmpty(ExternalFolder);
            
            LogToExternalFolderDisplayValue = IsLogToExternalFolderVisible ? TinkApp.LogToExternalFolder : false;

            IsSiteCachingOnDisplayValue = TinkApp.IsSiteCachingOn;

            if (TinkApp.Uris == null
                || TinkApp.Uris.Uris.Count <= 0)
            {
                throw new ArgumentException("Can not instantiate settings page view model- object. No uri- list available.");
            }

            if (string.IsNullOrEmpty(TinkApp.NextActiveUri.AbsoluteUri))
            {
                throw new ArgumentException("Can not instantiate settings page view model- object. Next active uri must not be null or empty.");
            }

            GroupFilter = new SettingsBikeFilterViewModel(
                TinkApp.FilterGroupSetting,
                TinkApp.ActiveUser.IsLoggedIn ? TinkApp.ActiveUser.Group : null);

            m_oViewUpdateManager = new IdlePollingUpdateTaskManager();

            Polling = new PollingViewModel(TinkApp.Polling);

            ExpiresAfterTotalSeconds = Convert.ToInt32(TinkApp.ExpiresAfter.TotalSeconds);

            CopriServerUriList = new CopriServerUriListViewModel(TinkApp.Uris);

            Themes = new ServicesViewModel(
                TinkApp.Themes.Select(x => x.GetType().FullName),
                    new Dictionary<string, string> {
                        { typeof(Themes.Konrad).FullName, "Konrad" },
                        { typeof(Themes.ShareeBike).FullName, "sharee.bike" }
                    },
                    TinkApp.Themes.Active.GetType().FullName);

            Themes.PropertyChanged += OnThemesChanged;


            LocksServices = new LocksServicesViewModel(
                TinkApp.LocksServices.Active.TimeOut.MultiConnect,
                new ServicesViewModel(
                    TinkApp.LocksServices,
                    new Dictionary<string, string> {
                        { typeof(LocksServiceInReach).FullName, "Simulation - AllLocksInReach" },
                        { typeof(LocksServiceOutOfReach).FullName, "Simulation - AllLocksOutOfReach" },
                        { typeof(Services.BluetoothLock.BLE.LockItByScanServiceEventBased).FullName, "Live - Scan" },
                        { typeof(Services.BluetoothLock.BLE.LockItByScanServicePolling).FullName, "Live - Scan (Polling)" },
                        { typeof(Services.BluetoothLock.BLE.LockItByGuidService).FullName, "Live - Guid" },
                        /* { typeof(Services.BluetoothLock.Arendi.LockItByGuidService).FullName, "Live - Guid (Arendi)" },
                        { typeof(Services.BluetoothLock.Arendi.LockItByScanService).FullName, "Live - Scan (Arendi)" },
                        { typeof(Services.BluetoothLock.Bluetoothle.LockItByGuidService).FullName, "Live - Guid (Ritchie)" }, */
                    },
                    TinkApp.LocksServices.Active.GetType().FullName));

            GeolocationServices = new ServicesViewModel(
                GeoloctionServicesContainer.Select(x => x.GetType().FullName),
                new Dictionary<string, string> { 
                    { typeof(LastKnownGeolocationService).FullName, "Smartdevice-LastKnowGeolocation" }, 
                    { typeof(GeolocationService).FullName, "Smartdevice-MediumAccuracy" }, 
                    { typeof(SimulatedGeolocationService).FullName, "Simulation-AlwaysSamePosition" } },
                GeoloctionServicesContainer.Active.GetType().FullName);
        }

        /// <summary>
        /// User switches scheme.
        /// </summary>
        private void OnThemesChanged(object sender, PropertyChangedEventArgs e)
        {
            // Set active theme (leads to switch of title)
            TinkApp.Themes.SetActive(Themes.Active);

            // Switch theme.
            ICollection<ResourceDictionary> mergedDictionaries = Application.Current.Resources.MergedDictionaries;
            if (mergedDictionaries == null)
            {
                Log.ForContext<SettingsPageViewModel>().Error("No merged dictionary available.");
                return;
            }

            mergedDictionaries.Clear();

            if (Themes.Active == typeof(Themes.Konrad).FullName)
            {
                mergedDictionaries.Add(new Themes.Konrad());
            }
            else if (Themes.Active == typeof(Themes.ShareeBike).FullName)
            {
                mergedDictionaries.Add(new Themes.ShareeBike());
            } 
            else
            {
                Log.ForContext<SettingsPageViewModel>().Debug($"No theme {Themes.Active} found.");
            }
        }

        /// <summary> Holds information whether app is connected to web or not. </summary>
        private bool? isConnected = null;

        /// <summary>Exposes the is connected state. </summary>
        private bool IsConnected
        {
            get => isConnected ?? false;
            set
            {
                isConnected = value;
            }
        }

        /// <summary> Holds a value indicating whether group filters GUI are visible or not</summary>
        public bool IsGroupFilterVisible => GroupFilter.Count > 0;

        /// <summary> Holds the bike types to fade out or show</summary>
        public SettingsBikeFilterViewModel GroupFilter { get; }

        /// <summary> Gets the value to path were copri mock files are located (for debugging purposes).</summary>
        public string ExternalFolder { get; }

        /// <summary>
        /// Gets the value to path were copri mock files are located (for debugging purposes).
        /// </summary>
        public string InternalPath => TinkApp.SettingsFileFolder;

        /// <summary>
        /// Gets the value of device identifier (for debugging purposes).
        /// </summary>
        public string DeviceIdentifier
        {
            get { return TinkApp.SmartDevice.Identifier; }
        }

        /// <summary> 
        /// Invoked when page is shutdown. 
        /// Currently invoked by code behind, would be nice if called by XAML in future versions.
        /// </summary>
        public async Task OnDisappearing()
        {
            try
            {
                Log.ForContext<SettingsPageViewModel>().Information($"Entering {nameof(OnDisappearing)}...");

                // Update model values.
                TinkApp.NextActiveUri = CopriServerUriList.NextActiveUri;

                TinkApp.Polling = new PollingParameters(
                    new TimeSpan(0, 0, Polling.PeriodeTotalSeconds),
                    Polling.IsActivated);

                TinkApp.ExpiresAfter = TimeSpan.FromSeconds(ExpiresAfterTotalSeconds);

                var filterGroup = GroupFilter.ToDictionary(x => x.Key, x => x.State);
                TinkApp.FilterGroupSetting = new GroupFilterSettings(filterGroup.Count > 0 ? filterGroup : null);

                // Update map page filter. 
                // Reasons for which map page filter has to be updated:
                // - user activated/ deactivated a group (TINKCorpi/ TINKSms/ Konrad)
                // - user logged off
                TinkApp.GroupFilterMapPage =
                    GroupFilterMapPageHelper.CreateUpdated(
                        TinkApp.GroupFilterMapPage,
                        TinkApp.ActiveUser.DoFilter(TinkApp.FilterGroupSetting.DoFilter()));

                TinkApp.CenterMapToCurrentLocation = CenterMapToCurrentLocation;

                if (IsLogToExternalFolderVisible)
                {
                    // If no external folder is available do not update model value.
                    TinkApp.LogToExternalFolder = LogToExternalFolderDisplayValue;
                }

                TinkApp.IsSiteCachingOn = IsSiteCachingOnDisplayValue;

                TinkApp.MinimumLogEventLevel = m_oMinimumLogEventLevel; // Update value to be serialized.
                TinkApp.UpdateLoggingLevel(m_oMinimumLogEventLevel); // Update logging server.

                TinkApp.IsReportLevelVerbose = IsReportLevelVerbose;

                TinkApp.LocksServices.SetActive(LocksServices.Services.Active);

                GeoloctionServicesContainer.SetActive(GeolocationServices.Active);

                TinkApp.LocksServices.SetTimeOut(TimeSpan.FromSeconds(LocksServices.ConnectTimeoutSec));

                // Persist settings in case app is closed directly.
                TinkApp.Save();

                TinkApp.UpdateConnector();

                await m_oViewUpdateManager.StopUpdatePeridically();

                Log.ForContext<SettingsPageViewModel>().Information($"{nameof(OnDisappearing)} done.");
            }
            catch (Exception l_oException)
            {
                await m_oViewService.DisplayAlert(
                    "Fehler",
                    $"Ein unerwarteter Fehler ist aufgetreten. \r\n{l_oException.Message}",
                    "OK");
            }
        }

        /// <summary> True if there is an error message to display.</summary>


        /// <summary>
        /// Exception which occurred getting bike information.
        /// </summary>
        protected Exception Exception { get; set; }

        /// <summary>
        /// If true debug controls are visible, false if not.
        /// </summary>
        public Permissions DebugLevel
        {
            get
            {
                return (Exception == null || Exception is WebConnectFailureException)
                    ? TinkApp.ActiveUser.DebugLevel
                    : Permissions.None;
            }
        }

        /// <summary>Polling periode.</summary>
        public PollingViewModel Polling { get; }

        /// <summary> Active logging level</summary>
        public string SelectedLoggingLevel
        {
            get
            {
                return m_oMinimumLogEventLevel.ToString();
            }
            set
            {
                if (!Enum.TryParse(value, out LogEventLevel l_oNewLevel))
                {
                    return;
                }

                m_oMinimumLogEventLevel = l_oNewLevel;
            }
        }

        public bool CenterMapToCurrentLocation { get; set; }

        /// <summary> Holds either
        /// - a value indicating whether to use external folder (e.g. SD card)/ or internal folder for storing log-files or
        /// - is false if external folder is not available
        /// </summary>
        public bool LogToExternalFolderDisplayValue { get; set; }

        public bool IsSiteCachingOnDisplayValue { get; set; }

        /// <summary> Holds a value indicating whether user can use external folder (e.g. SD card) for storing log-files.</summary>
        public bool IsLogToExternalFolderVisible { get; }

        /// <summary>
        ///  Holds the logging level serilog provides.
        /// </summary>
        public List<string> LoggingLevels
        {
            get
            {
                return new List<string>
                {
                    LogEventLevel.Verbose.ToString(),
                    LogEventLevel.Debug.ToString(),
                    LogEventLevel.Information.ToString(),
                    LogEventLevel.Warning.ToString(),
                    LogEventLevel.Error.ToString(),
                    LogEventLevel.Fatal.ToString(),
                };
            }
        }

        double expiresAfterTotalSeconds;

        public double ExpiresAfterTotalSeconds
        {
            get => expiresAfterTotalSeconds;
            set
            {
                if (value == expiresAfterTotalSeconds)
                {
                    return;
                }

                expiresAfterTotalSeconds = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ExpiresAfterTotalSecondsText)));
            }
        }

        public string ExpiresAfterTotalSecondsText
        {
            get => expiresAfterTotalSeconds.ToString("0");
        }


    }
}