#nullable enable

using System.Net;

namespace Bit.Test.Common.MockedHttpClient;

public class MockedHttpMessageHandler : HttpMessageHandler
{
    private readonly List<IHttpRequestMatcher> _matchers = new();

    public List<HttpRequestMessage> CapturedRequests { get; } = new List<HttpRequestMessage>();

    /// <summary>
    /// The fallback handler to use when the request does not match any of the provided matchers.
    /// </summary>
    /// <returns>A Matcher that responds with 404 Not Found</returns>
    public MockedHttpResponse Fallback { get; set; } = new(HttpStatusCode.NotFound);

    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        CapturedRequests.Add(request);
        var matcher = _matchers.FirstOrDefault(x => x.Matches(request));
        if (matcher == null)
        {
            return await Fallback.RespondToAsync(request);
        }

        return await matcher.RespondToAsync(request);
    }

    /// <summary>
    /// Instantiates a new HttpRequestMessage matcher that will handle requests in fitting with the returned matcher. Configuration can be chained.
    /// </summary>
    /// <param name="requestMatcher"></param>
    /// <typeparam name="T"></typeparam>
    /// <returns></returns>
    public T When<T>(T requestMatcher) where T : IHttpRequestMatcher
    {
        _matchers.Add(requestMatcher);
        return requestMatcher;
    }

    /// <summary>
    /// Instantiates a new HttpRequestMessage matcher that will handle requests in fitting with the returned matcher. Configuration can be chained.
    /// </summary>
    /// <param name="requestMatcher"></param>
    /// <typeparam name="T"></typeparam>
    /// <returns></returns>
    public HttpRequestMatcher When(string uri)
    {
        var matcher = new HttpRequestMatcher(uri);
        _matchers.Add(matcher);
        return matcher;
    }

    /// <summary>
    /// Instantiates a new HttpRequestMessage matcher that will handle requests in fitting with the returned matcher. Configuration can be chained.
    /// </summary>
    /// <param name="requestMatcher"></param>
    /// <typeparam name="T"></typeparam>
    /// <returns></returns>
    public HttpRequestMatcher When(Uri uri)
    {
        var matcher = new HttpRequestMatcher(uri);
        _matchers.Add(matcher);
        return matcher;
    }

    /// <summary>
    /// Instantiates a new HttpRequestMessage matcher that will handle requests in fitting with the returned matcher. Configuration can be chained.
    /// </summary>
    /// <param name="requestMatcher"></param>
    /// <typeparam name="T"></typeparam>
    /// <returns></returns>
    public HttpRequestMatcher When(HttpMethod method)
    {
        var matcher = new HttpRequestMatcher(method);
        _matchers.Add(matcher);
        return matcher;
    }

    /// <summary>
    /// Instantiates a new HttpRequestMessage matcher that will handle requests in fitting with the returned matcher. Configuration can be chained.
    /// </summary>
    /// <param name="requestMatcher"></param>
    /// <typeparam name="T"></typeparam>
    /// <returns></returns>
    public HttpRequestMatcher When(HttpMethod method, string uri)
    {
        var matcher = new HttpRequestMatcher(method, uri);
        _matchers.Add(matcher);
        return matcher;
    }

    /// <summary>
    /// Instantiates a new HttpRequestMessage matcher that will handle requests in fitting with the returned matcher. Configuration can be chained.
    /// </summary>
    /// <param name="requestMatcher"></param>
    /// <typeparam name="T"></typeparam>
    /// <returns></returns>
    public HttpRequestMatcher When(Func<HttpRequestMessage, bool> matcher)
    {
        var requestMatcher = new HttpRequestMatcher(matcher);
        _matchers.Add(requestMatcher);
        return requestMatcher;
    }

    /// <summary>
    /// Converts the MockedHttpMessageHandler to a HttpClient that can be used in your tests after setup.
    /// </summary>
    /// <returns></returns>
    public HttpClient ToHttpClient()
    {
        return new HttpClient(this);
    }
}