/**
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0.
 */

#pragma once

#include <assert.h>
#include <aws/core/client/ClientConfiguration.h>
#include <aws/core/http/HttpClient.h>
#include <aws/core/http/URI.h>
#include <aws/core/http/standard/StandardHttpRequest.h>
#include <aws/core/http/standard/StandardHttpResponse.h>
#include <aws/core/http/HttpClientFactory.h>
#include <aws/core/utils/UnreferencedParam.h>
#include <aws/core/utils/memory/stl/AWSVector.h>
#include <aws/core/utils/memory/stl/AWSQueue.h>

static const char MockHttpAllocationTag[] = "MockHttp";

class MockHttpClient : public Aws::Http::HttpClient
{
public:
    std::shared_ptr<Aws::Http::HttpResponse> MakeRequest(const std::shared_ptr<Aws::Http::HttpRequest>& request,
                                                         Aws::Utils::RateLimits::RateLimiterInterface* readLimiter = nullptr,
                                                         Aws::Utils::RateLimits::RateLimiterInterface* writeLimiter = nullptr) const override
    {
        AWS_UNREFERENCED_PARAM(readLimiter);
        AWS_UNREFERENCED_PARAM(writeLimiter);

        request->SetResolvedRemoteHost("127.0.0.1");
        //note that the mock client factory logically enforces type safety here.
        m_requestsMade.push_back(static_cast<const Aws::Http::Standard::StandardHttpRequest&>(*request));

        if (m_responsesToUse.size() > 0)
        {
            std::shared_ptr<Aws::Http::HttpResponse> responseToUse = m_responsesToUse.front();
            m_responsesToUse.pop();
            if (responseToUse)
            {
                responseToUse->SetOriginatingRequest(request);
            }
            return responseToUse;
        }
        return Aws::MakeShared<Aws::Http::Standard::StandardHttpResponse>(MockHttpAllocationTag, request);
    }

    const Aws::Http::Standard::StandardHttpRequest& GetMostRecentHttpRequest() const
    {
        assert(!m_requestsMade.empty());
        return m_requestsMade.back();
    }
    const Aws::Vector<Aws::Http::Standard::StandardHttpRequest>& GetAllRequestsMade() const { return m_requestsMade; }

    //these will be cleaned up by the aws client, so if you are testing an aws client, don't worry about freeing the memory
    //when you are finished.
    void AddResponseToReturn(const std::shared_ptr<Aws::Http::HttpResponse>& response) { m_responsesToUse.push(response); }

    void Reset()
    {
        m_requestsMade.clear();
        Aws::Queue<std::shared_ptr<Aws::Http::HttpResponse> > empty;
        std::swap(m_responsesToUse, empty);
    }

private:
    mutable Aws::Vector<Aws::Http::Standard::StandardHttpRequest> m_requestsMade;
    mutable Aws::Queue< std::shared_ptr<Aws::Http::HttpResponse> > m_responsesToUse;
};

class MockHttpClientFactory : public Aws::Http::HttpClientFactory
{
public:
    virtual std::shared_ptr<Aws::Http::HttpClient> CreateHttpClient(const Aws::Client::ClientConfiguration& clientConfiguration) const override
    {
        AWS_UNREFERENCED_PARAM(clientConfiguration);
        return m_clientToUse;
    }

    virtual std::shared_ptr<Aws::Http::HttpRequest> CreateHttpRequest(const Aws::String& uri, Aws::Http::HttpMethod method, const Aws::IOStreamFactory& streamFactory) const override
    {
        auto request = Aws::MakeShared<Aws::Http::Standard::StandardHttpRequest>(MockHttpAllocationTag, uri, method);
        request->SetResponseStreamFactory(streamFactory);

        return request;
    }

    virtual std::shared_ptr<Aws::Http::HttpRequest> CreateHttpRequest(const Aws::Http::URI& uri, Aws::Http::HttpMethod method, const Aws::IOStreamFactory& streamFactory) const override
    {
        auto request = Aws::MakeShared<Aws::Http::Standard::StandardHttpRequest>(MockHttpAllocationTag, uri, method);
        request->SetResponseStreamFactory(streamFactory);

        return request;
    }

    inline MockHttpClient& GetClient() const { return *m_clientToUse; }
    //Do not clean the client up when you are finished. The aws clients all delete their http client
    //upon finishing.
    inline void SetClient(const std::shared_ptr<MockHttpClient>& client) { m_clientToUse = client; }

private:
    std::shared_ptr<MockHttpClient> m_clientToUse;
};