using C4IT.FASD.Base;
using C4IT.FASD.Cockpit;
using C4IT.FASD.Cockpit.Communication;
using C4IT.Graphics;
using C4IT.Logging;
using C4IT.MultiLanguage;
using C4IT.Security;

using FasdCockpitBase.Models;

using FasdDesktopUi.Pages.SettingsPage;
using FasdDesktopUi.Pages.SplashScreenView;

using Microsoft.Win32;

using Newtonsoft.Json.Linq;

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Threading;
using static C4IT.Logging.cLogManager;

namespace FasdDesktopUi.Basics.Models
{
    public class cConnectionStatusHelper
    {
        public enum enumOnlineStatus
        {
            notSpecified = 0,
            offline,
            connectionError,
            incompatibleServerVersion,
            serverNotConfigured,
            serverStarting,
            illegalConfig,
            unauthorized,
            online
        }

        public enum enumCheckReason
        {
            firstStart = 0,
            lateInit,
            heartBeat
        }

        public bool IsActive = true;

        public readonly Version MinServerVersion = new Version("0.0.0.0");

        private enum enumCheckRunning { no = 0, running, again };

        private enumCheckReason checkReason = enumCheckReason.firstStart;

        public delegate void ConnectionStatusDelegate(enumOnlineStatus? Status);
        public static event ConnectionStatusDelegate ApiConnectionStatusChanged;

        public delegate cF4SdUserInfoChange M42FormBasedAuthenticationDelegate();
        public event M42FormBasedAuthenticationDelegate M42FormBasedAuthentication;

        public static cConnectionStatusHelper Instance { get; set; }

        public bool IsAuthorizationSupported { get; private set; } = false;
        private System.Timers.Timer timer;

        #region Lock Elements Connecion Status
        private readonly object connectionStatusCheckLock = new object();
        private enumCheckRunning IsConnectionStatusCheckRunning = enumCheckRunning.no;
        #endregion

        public enumOnlineStatus ApiConnectionStatus = enumOnlineStatus.notSpecified;

        public cConnectionStatusHelper()
        {
            ApiConnectionStatus = enumOnlineStatus.offline;
            cFasdCockpitCommunicationBase.Instance.CheckConnectionStatus = RunConnectionStatusCheckAsync;
            timer = new System.Timers.Timer();
            timer.Elapsed += Timer_Elapsed;

            Assembly assembly = Assembly.GetExecutingAssembly();
            var arrMinClientVersion = assembly.GetCustomAttributes(typeof(AssemblyMinServerVersion), false);
            if (arrMinClientVersion != null && arrMinClientVersion.Length >= 1)
            {
                var attrMinClientVersion = (AssemblyMinServerVersion)(arrMinClientVersion[0]);
                MinServerVersion = new Version(attrMinClientVersion.minServerVersion);
            }

        }

        private async void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) => await RunConnectionStatusCheckAsync();

        public async Task RunConnectionStatusCheckAsync()
        {
            await RunConnectionStatusCheckAsync(null);
        }

        private (int timerInterval, int shortInterval) GetTimerIntervalFromRegistry(int defaultInterval, int defaultShortInterval)
        {
            int timerInterval = defaultInterval;
            int shortInterval = defaultShortInterval;

            if (!cLogManager.DefaultLogger.IsDebug)
                return (timerInterval, shortInterval);

            try
            {
                var regBase = RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Registry32);
                var regKey = regBase.OpenSubKey("SOFTWARE\\Consulting4IT GmbH\\First Aid Service Desk\\Cockpit", false);

                if (regKey is null || !int.TryParse(regKey.GetValue("DebugConnectionCheck", 0).ToString(), out var regValue))
                    return (timerInterval, shortInterval);

                if (regValue <= 0)
                    return (timerInterval, shortInterval);

                timerInterval = regValue * 1000;
                shortInterval = timerInterval / 10;
            }
            catch { }

            return (timerInterval, shortInterval);
        }

        private void HandleConnectionStatus(enumConnectionStatus status, ref int timerInterval, int timerInteralShort)
        {
            switch (status)
            {
                case enumConnectionStatus.unknown:
                case enumConnectionStatus.serverNotFound:
                    if (ApiConnectionStatus != enumOnlineStatus.offline)
                        ApiConnectionStatus = enumOnlineStatus.offline;
                    timerInterval = timerInteralShort;
                    LogEntry("RunConnectionStatusCheckAsync: Exit due to status 'serverNotFound'");
                    break;
                case enumConnectionStatus.serverResponseError:
                    ApiConnectionStatus = enumOnlineStatus.connectionError;
                    timerInterval = timerInteralShort;
                    LogEntry("RunConnectionStatusCheckAsync: Exit due to status 'serverResponseError'");
                    break;
                case enumConnectionStatus.incompatibleServerVersion:
                    LogEntry("RunConnectionStatusCheckAsync: Exit due to status 'incompatibleServerVersion'");
                    ApiConnectionStatus = enumOnlineStatus.incompatibleServerVersion;
                    break;
                case enumConnectionStatus.serverStarting:
                    ApiConnectionStatus = enumOnlineStatus.serverStarting;
                    break;
                case enumConnectionStatus.serverNotConfigured:
                    ApiConnectionStatus = enumOnlineStatus.serverNotConfigured;
                    break;
                case enumConnectionStatus.connected:
                    if (cCockpitConfiguration.Instance == null || cF4SDCockpitXmlConfig.Instance == null)
                        ApiConnectionStatus = enumOnlineStatus.illegalConfig;
                    else if (ApiConnectionStatus != enumOnlineStatus.online)
                        ApiConnectionStatus = enumOnlineStatus.online;
                    break;
            }
        }

        public async Task RunConnectionStatusCheckAsync(SplashScreenView splashScreen)
        {
            if (!IsActive)
                return;

            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);
            try
            {
                (int timerInterval, int shortTimerInterval) = GetTimerIntervalFromRegistry(300000, 30000);
                enumOnlineStatus oldConnectionStatus = ApiConnectionStatus;

                try
                {
                    timer.Stop();
                    lock (connectionStatusCheckLock)
                    {
                        switch (IsConnectionStatusCheckRunning)
                        {
                            case enumCheckRunning.again:
                                LogEntry("RunConnectionStatusCheckAsync is already running. Status is 'again'.");
                                return;
                            case enumCheckRunning.running:
                                LogEntry("RunConnectionStatusCheckAsync is already running. Status is 'running'.");
                                IsConnectionStatusCheckRunning = enumCheckRunning.again;
                                return;
                            default:
                                IsConnectionStatusCheckRunning = enumCheckRunning.running;
                                break;
                        }
                    }

                    cCheckConnectionResult connectionResult = await cFasdCockpitCommunicationBase.Instance.CheckConnection(MinServerVersion);
                    IsAuthorizationSupported = connectionResult?.ApiConnectionInfo?.SupportAuthorisation ?? false;

                    HandleConnectionStatus(connectionResult.ConnectionStatus, ref timerInterval, shortTimerInterval);
                    if (connectionResult.ConnectionStatus != enumConnectionStatus.connected)
                        return;

                    if (checkReason < enumCheckReason.heartBeat && ApiConnectionStatus == enumOnlineStatus.illegalConfig)
                    {
                        // download the xml config files
                        Task<bool> loadConfigFilesTask = Task.Run(async () => await cFasdCockpitConfig.Instance.LoadConfigFilesAsync(cFasdCockpitCommunicationBase.Instance));
                        // get the cockpit base configuration from server
                        Task<bool> getCockpitConfig = Task.Run(async () => await cFasdCockpitConfig.Instance.GetCockpitConfigurationAsync());

                        Dispatcher.CurrentDispatcher.Invoke(() => splashScreen?.SetStatusText(cMultiLanguageSupport.GetItem("StartUp.SplashScreen.LoadConfigs")));
                        var configTasks = await Task.WhenAll(loadConfigFilesTask, getCockpitConfig);

                        if (configTasks.Any(t => t == false))
                            return;
                        if (cFasdCockpitConfig.Instance?.Global != null && cCockpitConfiguration.Instance?.GlobalConfig != null)
                        {
                            cFasdCockpitConfig.Instance.Global.Load(cCockpitConfiguration.Instance.GlobalConfig);
                            cFasdCockpitConfig.Instance.Global.Save();
                        }
                    }

                    cF4sdUserInfo userInfo;
                    lock (cFasdCockpitCommunicationBase.CockpitUserInfoLock)
                    {
                        userInfo = cFasdCockpitCommunicationBase.CockpitUserInfo;
                    }
                    if (IsAuthorizationSupported)
                    {
                        if (userInfo is null || DateTime.UtcNow > userInfo.RenewUntil)
                        {
                            Dispatcher.CurrentDispatcher.Invoke(() => splashScreen?.SetStatusText(cMultiLanguageSupport.GetItem("StartUp.SplashScreen.AuthenticateUser")));
                            ApiConnectionStatus = enumOnlineStatus.unauthorized;
                            const string cockpitUserRole = "Cockpit.User";
#if isNewFeature
                            const string cockpitTicketAgentRole = "Cockpit.TicketAgent";
#endif
                            userInfo = await cFasdCockpitCommunicationBase.Instance.WinLogon();
                            lock (cFasdCockpitCommunicationBase.CockpitUserInfoLock)
                            {
                                cFasdCockpitCommunicationBase.CockpitUserInfo = userInfo;
                            }
                            if (userInfo?.Roles is null || !userInfo.Roles.Contains(cockpitUserRole))
                            {
                                Dispatcher.CurrentDispatcher.Invoke(() => splashScreen?.SetStatusText(cMultiLanguageSupport.GetItem("StartUp.SplashScreen.NoAuthorization")));
                                LogEntry($"Cockpit User ({userInfo?.Name} with Id {userInfo?.Id}, has not the required permissions.", LogLevels.Error);
                            }
                            else
                            {
                                await Task.Run(async () => await cFasdCockpitConfig.Instance.InstantiateAnalyticsAsync(cFasdCockpitConfig.SessionId));
                                ApiConnectionStatus = enumOnlineStatus.online;
#if isNewFeature
                                if (userInfo.Roles.Contains(cockpitTicketAgentRole))
                                    cCockpitConfiguration.Instance.ticketSupport.EditTicket = true;
#endif
                            }
                        }
                    }
                    else
                    {
                        if (userInfo == null)
                        {
                            string cockpitUserName = Environment.UserName;
                            string cockpitUserDomain = Environment.UserDomainName;
                            Guid cockpitUserId = await cFasdCockpitCommunicationBase.Instance.GetUserIdByAccount(cockpitUserName, cockpitUserDomain);
                            if (cockpitUserId == Guid.Empty)
                                LogEntry($"Could not get UserId for cockpit user '{cockpitUserName}@{cockpitUserDomain}'.", LogLevels.Warning);
                            else
                            {
                                userInfo = new cF4sdUserInfo()
                                {
                                    Id = cockpitUserId,
                                    AccountType = cF4sdUserInfo.enumAccountType.unknown,
                                };
                                lock (cFasdCockpitCommunicationBase.CockpitUserInfoLock)
                                {
                                    cFasdCockpitCommunicationBase.CockpitUserInfo = userInfo;
                                }
                            }
                        }
                        ApiConnectionStatus = enumOnlineStatus.online;
                    }

                    if (App.M42OptionMenuItem != null)
                        App.Current.MainWindow.Dispatcher.Invoke(() =>
                        {
                                App.M42OptionMenuItem.Enabled = userInfo != null;
                        });

                    // check, if the are logons needed
                    bool m42Valid = await CheckAndRefreshM42LogonAsync();
                    await cFasdCockpitConfig.Instance.CheckAgentScriptAvailabilityAsync();
                    await cFasdCockpitConfig.Instance.CheckServerQuickActionAvailabilityAsync();
                    await cFasdCockpitCommunicationBase.Instance.InitializeAfterOnlineAsync();
                    cFasdCockpitConfig.Instance.OnUiSettingsChanged();
                }
                catch (Exception E)
                {
                    LogException(E);
                }
                finally
                {
                    try
                    {
                        lock (connectionStatusCheckLock)
                        {
                            if (IsConnectionStatusCheckRunning == enumCheckRunning.again)
                                timerInterval = 1;
                            IsConnectionStatusCheckRunning = enumCheckRunning.no;
                        }
                    }
                    catch (Exception E)
                    {
                        LogException(E);
                    }

                    if (IsActive)
                    {
                        if (ApiConnectionStatus == enumOnlineStatus.online)
                            NotifyerSupport.SetNotifyIcon("Default", null, NotifyerSupport.enumIconAlignment.BottomRight);
                        else
                            NotifyerSupport.SetNotifyIcon("Default", "OverlayOffline", NotifyerSupport.enumIconAlignment.BottomRight);
                        if (ApiConnectionStatus != oldConnectionStatus)
                            OnApiConnectionStatusChanged();
                    }
                    if (timer != null)
                    {
                        timer.Interval = timerInterval;
                        timer.Start();
                    }
                }
            }
            catch (Exception E)
            {
                LogException(E);
            }
            finally
            {
                LogMethodEnd(CM);
            }
        }

        private async Task<bool> CheckAndRefreshM42LogonAsync()
        {
            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);
            try
            {
                var userInfo = cFasdCockpitCommunicationBase.CockpitUserInfo;

                if (userInfo == null)
                    return false;

                // do we have a valid M42 token?
                if (userInfo?.ValidLogonsUntil != null && userInfo.ValidLogonsUntil.TryGetValue(enumAdditionalAuthentication.M42WinLogon, out var logonPeriod))
                {
                    if (logonPeriod.renewUntil > DateTime.UtcNow)
                        return true;
                }

                // do we need a logon?
                var isNeeded = false;
                if (cFasdCockpitConfig.Instance?.M42Config == null)
                    return false;

                cF4sdCockpitConfigM42 config = cFasdCockpitConfig.Instance.M42Config;
                if (config.Control == enumM42AuthenticationControl.always)
                    isNeeded = true;
                else if (config.Control == enumM42AuthenticationControl.auto)
                {
                    if (userInfo?.additionalLogons == null)
                        return false;

                    if (userInfo.additionalLogons.Contains(enumAdditionalAuthentication.M42WinLogon))
                        isNeeded = true;
                }

                if (!isNeeded)
                    return false;

                // yes, we need a new logon
                cF4sdCockpitM42BearerTokenInfo tokenInfo = null;
                cF4SdUserInfoChange userChange = null;
                switch (config.Method)
                {
                    case enumM42AuthenticationMethod.passthrough:
                        tokenInfo = await cFasdCockpitCommunicationBase.Instance.M42.ValidateLogonPassthrough();
                        break;
                    case enumM42AuthenticationMethod.basic:
                        var user = config.BasicUser;
                        var pw = PrivateSecurePassword.Instance.Decode(config.BasicPassword);
                        tokenInfo = await cFasdCockpitCommunicationBase.Instance.M42.ValidateLogonBasic(user, pw);
                        break;
                    case enumM42AuthenticationMethod.token:
                        var token = PrivateSecurePassword.Instance.Decode(config.ApiToken);
                        tokenInfo = await cFasdCockpitCommunicationBase.Instance.M42.ValidateLogonToken(token);
                        break;
                    case enumM42AuthenticationMethod.forms:
                        var FormBasedAuth = M42FormBasedAuthentication;
                        userChange = FormBasedAuth?.Invoke();
                        break;
                }

                if (tokenInfo?.Token != null && tokenInfo.ValidUntil > DateTime.UtcNow)
                {
                    var tokenRegistration = new cF4SDTokenRegistration()
                    {
                        UserId = userInfo.Id,
                        TokenType = cF4SDTokenRegistration.enumTokenType.M42Bearer,
                        Secret = tokenInfo.Token
                    };

                    userChange = await cFasdCockpitCommunicationBase.Instance.RegisterExternalTokenAsync(tokenRegistration);
                }

                if (userChange != null)
                {
                    lock (cFasdCockpitCommunicationBase.CockpitUserInfoLock)
                    {
                        userChange.ChangeUserInfo(cFasdCockpitCommunicationBase.CockpitUserInfo);
                    }

                    return true;
                }
            }
            catch (Exception E)
            {
                LogException(E);
            }
            finally
            {
                LogMethodEnd(CM);
            }

            return false;
        }

        public void OnApiConnectionStatusChanged()
        {
            ConnectionStatusDelegate handler = ApiConnectionStatusChanged;
            handler?.Invoke(ApiConnectionStatus);
        }
    }
}
