using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net.Http.Headers;
using Newtonsoft.Json;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using System.Xml.Linq;
using Pilz.Networking.CloudProviders.Nextcloud.Client.Cloud;
using Pilz.Networking.CloudProviders.Nextcloud.Client.Core;
using Pilz.Networking.CloudProviders.Nextcloud.Client.LoginFlowV2;
using Pilz.Networking.CloudProviders.Nextcloud.Ocs.Responses;

namespace Pilz.Networking.CloudProviders.Nextcloud.Ocs
{
    public class OcsApi : IDisposable
    {
        public const string CONTENT_TYPE_JSON = "application/json";

        public event GetOcsApiAuthCredentailsEventHandler? GetOcsApiAuthCredentails;

        private readonly HttpClient client = new();
        private readonly List<OcsApiBase> apis = new();

        public string BaseUrl { get; set; } = string.Empty;

        public OcsApiLoginFlowV2 LoginFlowV2 => GetApi<OcsApiLoginFlowV2>();
        public OcsApiCore Core => GetApi<OcsApiCore>();
        public OcsApiCloud Cloud => GetApi<OcsApiCloud>();

        public TApi GetApi<TApi>() where TApi : OcsApiBase
        {
            var instance = TryGetApi<TApi>();
            return instance is null ? throw new NullReferenceException() : instance;
        }

        public TApi? TryGetApi<TApi>() where TApi : OcsApiBase
        {
            TApi? instance = (TApi?)apis.FirstOrDefault(n => n is TApi);

            instance ??= (TApi?)Activator.CreateInstance(typeof(TApi), new object[] { this });

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

            return instance;
        }

        public string BuildFullUrl(OcsApiUrlPath path)
        {
            return BaseUrl + path;
        }

        /// <summary>
        /// Makes an request with the given arguments and deserialize it to the given type.
        /// </summary>
        /// <typeparam name="TResponse"></typeparam>
        /// <param name="httpMethod"></param>
        /// <param name="url"></param>
        /// <param name="useAuthentication"></param>
        /// <param name="parameters"></param>
        /// <param name="content"></param>
        /// <returns>Returns the given OcsResponse type from the deserialized OcsApiResponse content.</returns>
        public TResponse? MakeRequestOcs<TResponse>(HttpMethod httpMethod, OcsApiUrlPath url, bool useAuthentication = true, Dictionary<string, string>? parameters = null, object? content = null) where TResponse : IOcsResponse
        {
            return MakeRequestOcs<TResponse>(httpMethod, BuildFullUrl(url), useAuthentication: useAuthentication, parameters: parameters, content: content);
        }

        /// <summary>
        /// Makes an request with the given arguments and deserialize it to the given type.
        /// </summary>
        /// <typeparam name="TResponse"></typeparam>
        /// <param name="httpMethod"></param>
        /// <param name="url"></param>
        /// <param name="useAuthentication"></param>
        /// <param name="parameters"></param>
        /// <param name="content"></param>
        /// <returns>Returns the given OcsResponse type from the deserialized OcsApiResponse content.</returns>
        public TResponse? MakeRequestOcs<TResponse>(HttpMethod httpMethod, string url, bool useAuthentication = true, Dictionary<string, string>? parameters = null, object? content = null) where TResponse : IOcsResponse
        {
            var response = MakeRequest<OcsApiResponse<TResponse>?>(httpMethod, url, useAuthentication: useAuthentication, parameters: parameters, content: content);

            if (response != null)
                return response.Ocs;

            return default;
        }

        /// <summary>
        /// Makes an request with the given arguments and deserialize it to the given type.
        /// </summary>
        /// <typeparam name="TResponse"></typeparam>
        /// <param name="httpMethod"></param>
        /// <param name="url"></param>
        /// <param name="useAuthentication"></param>
        /// <param name="parameters"></param>
        /// <param name="content"></param>
        /// <returns>Returns the deserialized content of type given type.</returns>
        public TResponse? MakeRequest<TResponse>(HttpMethod httpMethod, OcsApiUrlPath url, bool useAuthentication = true, Dictionary<string, string>? parameters = null, object? content = null)
        {
            return MakeRequest<TResponse>(httpMethod, BuildFullUrl(url), useAuthentication: useAuthentication, parameters: parameters, content: content);
        }

        /// <summary>
        /// Makes an request with the given arguments and deserialize it to the given type.
        /// </summary>
        /// <typeparam name="TResponse"></typeparam>
        /// <param name="httpMethod"></param>
        /// <param name="url"></param>
        /// <param name="useAuthentication"></param>
        /// <param name="parameters"></param>
        /// <param name="content"></param>
        /// <returns>Returns the deserialized content of type given type.</returns>
        public TResponse? MakeRequest<TResponse>(HttpMethod httpMethod, string url, bool useAuthentication = true, Dictionary<string, string>? parameters = null, object? content = null)
        {
            using var responseInit = MakeRequest(httpMethod, url, useAuthentication: useAuthentication, parameters: parameters, content: content);

            if (responseInit != null)
            {
                var bodyInit = responseInit.Content.ReadAsStringAsync().Result;
                return JsonConvert.DeserializeObject<TResponse>(bodyInit);
            }

            return default;
        }

        /// <summary>
        /// Makes an request with the given arguments.
        /// </summary>
        /// <param name="httpMethod"></param>
        /// <param name="url"></param>
        /// <param name="useAuthentication"></param>
        /// <param name="parameters"></param>
        /// <param name="content"></param>
        /// <returns>Returns a HttpResponseMessage as result.</returns>
        public HttpResponseMessage MakeRequest(HttpMethod httpMethod, OcsApiUrlPath url, bool useAuthentication = true, Dictionary<string, string>? parameters = null, object? content = null)
        {
            return MakeRequest(httpMethod, BuildFullUrl(url), useAuthentication: useAuthentication, parameters: parameters, content: content);
        }

        /// <summary>
        /// Makes an request with the given arguments.
        /// </summary>
        /// <param name="httpMethod"></param>
        /// <param name="url"></param>
        /// <param name="useAuthentication"></param>
        /// <param name="parameters"></param>
        /// <param name="content"></param>
        /// <returns>Returns a HttpResponseMessage as result.</returns>
        public HttpResponseMessage MakeRequest(HttpMethod httpMethod, string url, bool useAuthentication = true, Dictionary<string, string>? parameters = null, object? content = null)
        {
            OcsApiAuthCredentials? authentication;
            string @params;
            HttpContent? httpContent;

            // Get authentication
            if (useAuthentication)
            {
                var args = new GetOcsApiAuthCredentailsEventArgs();
                GetOcsApiAuthCredentails?.Invoke(this, args);
                authentication = args.Credentials;
            }
            else
                authentication = null;

            // Parse params
            if (parameters != null)
                @params = "?" + string.Join("&", parameters.Select(p => $"{p.Key}={p.Value}"));
            else
                @params = string.Empty;

            // Create content
            if (content is HttpContent contentHttp)
                httpContent = contentHttp;
            else if (content is OcsData || content is not null)
            {
                var stringContent = JsonConvert.SerializeObject(content);
                httpContent = new StringContent(stringContent, null, CONTENT_TYPE_JSON);
            }
            else
                httpContent = null;

            // Send request
            var request = new HttpRequestMessage
            {
                Method = httpMethod ?? HttpMethod.Post,
                RequestUri = new Uri(url + @params),
                Headers =
                {
                    { "Accept", CONTENT_TYPE_JSON },
                    { "OCS-APIREQUEST", "true" },
                    { "Authorization", authentication.ToBasicAuth() }
                },
                Content = httpContent
            };

            return client.Send(request);
        }

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