using Castle.Core.Logging;
using System.Web;

namespace Pilz.Net.Api;

public class ApiClient(string apiUrl) : IApiClient
{
    protected readonly List<object> clients = [];

    public event ApiRequestCancelEventHandler? OnApiRequestStart;
    public event ApiRequestEventHandler? OnApiRequestResponse;
    public event ApiRequestEventHandler? OnApiRequestCompleted;

    public HttpClient HttpClient { get; set; } = new();

    public virtual string ApiUrl { get; } = apiUrl;

    public string? AuthKey { get; set; }

    public virtual IApiMessageSerializer Serializer { get; set; } = new DefaultApiMessageSerializer();

    public ILogger Log { get; set; } = NullLogger.Instance;

    public T GetClient<T>() where T : IApiSubClient
    {
        foreach (var c in clients)
        {
            if (c is T t)
                return t;
        }

        if (T.CreateClient<T>(this) is T client)
        {
            clients.Add(client);
            return client;
        }

        throw new ApiException("Could not create an instance of this client.");
    }

    public virtual async Task<ApiResponse> SendRequest(string route, HttpMethod method, ApiMessage? message, ApiParameterCollection? @params, IApiMessageSerializer? serializer)
    {
        Log.InfoFormat("Send message to {0}", route);

        var context = new ApiRequestContext(route, method, message, @params, serializer, null);
        await Send(context);

        var response = new ApiResponse(context.HttpResponse);
        context.ApiResponse = response;

        return response;
    }

    public virtual async Task<ApiResponse<TResponse>> SendRequest<TResponse>(string route, HttpMethod method, ApiMessage? message, ApiParameterCollection? @params, IApiMessageSerializer? serializer) where TResponse : ApiMessage
    {
        Log.InfoFormat("Send request to {0}", route);

        var context = new ApiRequestContext(route, method, message, @params, serializer, typeof(TResponse));
        await Send(context);
        TResponse? result = null;

        if (context.HttpResponse != null && context.HttpResponse.IsSuccessStatusCode)
        {
            var mediaType = context.HttpResponse.Content.Headers.ContentType?.MediaType;
            if (!string.IsNullOrWhiteSpace(mediaType))
            {
                if (mediaType == "application/json" && context.Serializer != null)
                    result = context.Serializer.Deserialize(await context.HttpResponse.Content.ReadAsStringAsync(), typeof(TResponse)) as TResponse;
                else if (typeof(TResponse).IsAssignableTo(typeof(ApiRawMessage)) && mediaType == "application/octet-stream")
                    result = (TResponse)(object)new ApiRawMessage(await context.HttpResponse.Content.ReadAsByteArrayAsync());
            }
        }

        var response = new ApiResponse<TResponse>(context.HttpResponse, result);
        context.ApiResponse = response;

        return response;
    }

    protected virtual async Task Send(ApiRequestContext context)
    {
        var url = ApiUrl + context.Route + BuildParameters(context.Params);
        HttpContent content;
        context.Serializer ??= Serializer;

        Log.DebugFormat("Api endpoint url is {0}", url);

        if (OnApiRequestStart != null)
        {
            var args = new ApiRequestCancelEventArgs(context);
            OnApiRequestStart.Invoke(this, args);
            if (args.Cancel)
            {
                context.Canceled = true;
                return;
            }
        }

        Log.Debug("Create content");

        if (context.Message is not null)
            content = new StringContent(context.Serializer.Serialize(context.Message)!, null, "application/json");
        else
            content = new StringContent(string.Empty, null, "application/json");

        Log.Debug("Build headers");

        content.Headers.Add("API-AUTH-KEY", EncodeAuthKey());

        Log.Debug("Sending request");

        var httpmsg = new HttpRequestMessage(context.Method, url)
        {
            Content = content
        };

        context.HttpResponse = await HttpClient.SendAsync(httpmsg);

        OnApiRequestResponse?.Invoke(this, new(context));
    }

    protected virtual string BuildParameters(ApiParameterCollection? @params)
    {
        if (@params == null || @params.Count == 0)
            return string.Empty;

        return "?" + string.Join("&", @params.Select(kvp =>
        {
            var key = kvp.Key;
            var value = HttpUtility.UrlEncode(ConvertParameter(kvp.Value));
            return $"{key}={value}";
        }));
    }

    protected virtual string? ConvertParameter(object? @param)
    {
        return (string?)Convert.ChangeType(@param, typeof(string));
    }

    protected virtual string? EncodeAuthKey()
    {
        return AuthKey;
    }
}
