using DigitalData.Core.Client.Interface;
using System.Diagnostics.CodeAnalysis;
using System.Net;
using System.Web;

namespace DigitalData.Core.Client
{
    public class BaseHttpClientService : IBaseHttpClientService
    {
        protected readonly HttpClient _client;
        protected readonly CookieContainer _cookies;

        [StringSyntax("Uri")]
        protected readonly string _uri;

        protected readonly string _path;

        protected IEnumerable<KeyValuePair<string, object>>? _headers;

        protected IEnumerable<KeyValuePair<string, object?>>? _queryParams;

        internal BaseHttpClientService(HttpClient client, CookieContainer cookieContainer, IHttpClientOptions clientOptions)
        {
            _client = client;
            _cookies = cookieContainer;
            _uri = clientOptions.Uri.Trim(URI_TRIM_CHARS);
            _path = clientOptions.Path?.Trim(URI_TRIM_CHARS) ?? string.Empty;
            _headers = clientOptions.Headers;
            _queryParams = clientOptions.QueryParams;
        }

        public CookieCollection GetCookies(string path = "") => _cookies.GetCookies(uri: new Uri(UriCombine(_uri, path, path.Trim(URI_TRIM_CHARS))));

        public async Task<HttpResponseMessage> FetchAsync(
            string? scheme = null,
            int? port = null,
            string path = "",
            IEnumerable<KeyValuePair<string, object?>>? queryParams = null,
            HttpMethod? method = null,
            HttpContent? body = null,
            IEnumerable<KeyValuePair<string, object>>? form = null,
            IEnumerable<KeyValuePair<string, object>>? headers = null,
            bool sendWithCookie = true,
            bool saveCookie = true
        )
        {
            // merge with default headers
            if(_headers is not null)
            {
                if (headers is null)
                    headers = _headers;
                else
                {
                    var mergedHeaders = headers.ToList();
                    mergedHeaders.AddRange(_headers);
                    headers = mergedHeaders;
                }
            }

            // Add default query parameters
            if(_queryParams is not null)
            {
                if (queryParams is null)
                    queryParams = _queryParams;
                else
                {
                    var mergedQueryParams = queryParams.ToList();
                    mergedQueryParams.AddRange(_queryParams);
                    queryParams = mergedQueryParams;
                }
            }

            // set default HTTP method as GET
            method ??= HttpMethod.Get;

            // create URL
            var uriBuilder = new UriBuilder(_uri);
            if (scheme is not null)
                uriBuilder.Scheme = scheme;
            if (port is int portInt)
                uriBuilder.Port = portInt;
            uriBuilder.Path = UriCombine(_path, path?.Trim(URI_TRIM_CHARS) ?? string.Empty);

            // Add query parameters if provided
            if (queryParams?.Any() ?? false)
            {
                var query = HttpUtility.ParseQueryString(uriBuilder.Query);

                var flagParams = queryParams.Where(param => param.Value is null).Select(param => param.Key);

                var valueParams = queryParams.Where(param => param.Value is not null);

                foreach (var param in valueParams)
                    query[param.Key] = param.Value switch
                    {
                        bool b => b.ToString().ToLower(),
                        _ => param.Value.ToString()
                    };

                if (flagParams.Any())
                    uriBuilder.Query = string.Join(QUERY_SEPARATOR, query.ToString(), string.Join(QUERY_SEPARATOR, flagParams));
                else uriBuilder.Query = query.ToString();
            }

            var requestUri = uriBuilder.Uri;
            
            var requestMessage = new HttpRequestMessage(method, requestUri);

            // Add headers if provided
            headers?.ForEach(header => requestMessage.Headers.Add(header.Key, header.Value.ToString()));

            // Add cookie to request
            if (sendWithCookie)
            {
                var cookieHeader = _cookies.GetCookieHeader(requestUri);
                if (!string.IsNullOrWhiteSpace(cookieHeader))
                {
                    requestMessage.Headers.Add("Cookie", cookieHeader);
                }
            }

            // Add body content if provided
            if (body != null && form != null)
                throw new InvalidOperationException("Body content and form data cannot both be set.");
            else if (body != null)
                requestMessage.Content = body;
            else if (form != null)
                requestMessage.Content = new FormUrlEncodedContent(form.Select(e => KeyValuePair.Create(e.Key, e.Value.ToString())));

            var response = await _client.SendAsync(requestMessage);

            // Add response cookies to cookies
            if (saveCookie)
                if (response.Headers.TryGetValues("Set-Cookie", out var cookies))
                    foreach (var cookie in cookies)
                        _cookies.SetCookies(requestUri, cookie);

            return response;
        }

        internal static readonly char[] URI_TRIM_CHARS = { '\\', '/', ' ' };

        internal static string UriCombine(params string[] paths) => System.IO.Path.Combine(paths).Replace("\\", "/");

        internal static readonly char QUERY_SEPARATOR = '&';
    }
}