using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Pilz.Networking.CloudProviders.Nextcloud.Client;
using Pilz.Networking.CloudProviders.Nextcloud.Client.Cloud;
using Pilz.Networking.CloudProviders.Nextcloud.Client.Cloud.Model;
using Pilz.Networking.CloudProviders.Nextcloud.Client.LoginFlowV2.Ocs;
using Pilz.Networking.CloudProviders.Nextcloud.Ocs;

namespace Pilz.Networking.CloudProviders.Nextcloud
{
    public class NextcloudClient : IDisposable
    {
        private readonly List<ClientBase> clients = new();
        private NextcloudLogin? currentLogin;

        public OcsApi Ocs { get; init; } = new();

        public NextcloudLogin? CurrentLogin
        {
            get => currentLogin;
            private set
            {
                currentLogin = value;
                Ocs.BaseUrl = value?.Server ?? string.Empty;
            }
        }

        public CloudClient Cloud => GetClient<CloudClient>();

        public NextcloudClient()
        {
            Ocs.GetOcsApiAuthCredentails += Ocs_GetOcsApiAuthCredentails;
        }

        private void Ocs_GetOcsApiAuthCredentails(object sender, GetOcsApiAuthCredentailsEventArgs eventArgs)
        {
            if (sender == Ocs)
                eventArgs.Credentials = CurrentLogin.ToOcsApiAuthCredentials();
        }

        public TClient GetClient<TClient>() where TClient : ClientBase
        {
            var instance = TryGetClient<TClient>();
            return instance is null ? throw new NullReferenceException() : instance;
        }

        public TClient? TryGetClient<TClient>() where TClient : ClientBase
        {
            TClient? instance = (TClient?)clients.FirstOrDefault(n => n is TClient);
            
            instance ??= (TClient?)Activator.CreateInstance(typeof(TClient), new object[] { this });

            if (instance is not null)
                clients.Add(instance);

            return instance;
        }

        public UserInfo? Login(NextcloudLogin login)
        {
            return Login(login, true);
        }

        public UserInfo? Login(NextcloudLogin login, bool checkUser)
        {
            // Ensure we are logged out 
            Logout(false);

            // Temporary set user login
            CurrentLogin = login;

            if (checkUser)
            {
                // Try get user info & check if user is enabled
                var userInfo = Cloud.GetUserInfo();
                var isValid = userInfo != null && userInfo.Enabled;

                // If invalid, reset login credentials
                if (!isValid)
                    CurrentLogin = null;

                return userInfo;
            }

            return null;
        }

        public NextcloudLogin? Login(string baseUrl, CancellationToken cancellationToken)
        {
            // Ensure we are logged out
            Logout(false);

            // Init the login process
            var initResponse = Ocs.LoginFlowV2.Init(baseUrl);

            if (!string.IsNullOrEmpty(initResponse?.LoginUrl) && initResponse.Poll != null)
            {
                // Open the browser
                Process.Start(new ProcessStartInfo
                {
                    FileName = initResponse.LoginUrl,
                    UseShellExecute = true
                });

                // Poll for credentails in intervals
                OcsResponseLoginFlowV2Credentials? pollResponse = null;
                while (!cancellationToken.IsCancellationRequested && pollResponse is null)
                {
                    // Wait 5 seconds
                    Thread.Sleep(5000);

                    // Poll the credentials
                    if (!cancellationToken.IsCancellationRequested)
                        pollResponse = Ocs.LoginFlowV2.Poll(initResponse.Poll);
                }

                // Check login credentials
                if (pollResponse is not null)
                    CurrentLogin = new(pollResponse);
            }

            return CurrentLogin;
        }

        public void Logout()
        {
            Logout(false);
        }

        public void Logout(bool logoutOnServer)
        {
            if (CurrentLogin != null)
            {
                // Delete currently used app password
                if (logoutOnServer)
                    Ocs.Core.DeleteAppPassword();

                // Reset current login infos
                CurrentLogin = null;
            }
        }

        public void Dispose()
        {
            Ocs.Dispose();
            GC.SuppressFinalize(this);
        }
    }
}
