/*
 * Copyright 2022 Bloomberg Finance LP
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <buildboxcasd_fslocalactionstorage.h>
#include <buildboxcasd_localacproxyinstance.h>
#include <buildboxcasd_localcasinstance.h>
#include <buildboxcasd_proxynoupdatefixture.h>
#include <buildboxcommon_casclient.h>
#include <buildboxcommon_filestager.h>
#include <buildboxcommon_fileutils.h>
#include <buildboxcommon_fslocalcas.h>
#include <buildboxcommon_grpcclient.h>
#include <buildboxcommon_logging.h>
#include <buildboxcommon_lrulocalcas.h>
#include <buildboxcommon_temporarydirectory.h>
#include <cstdlib>
#include <gtest/gtest.h>

#include <grpcpp/server_context.h>

using namespace buildboxcasd;

namespace {
const auto digestFunctionInitializer = []() {
    buildboxcommon::DigestGenerator::init();
    return 0;
}();
} // namespace

class ProxyParamFixture : public ProxyNoUpdateFixture {
  protected:
    ProxyParamFixture() {}

  public:
    ActionResult action_result_a;
    Digest action_digest_a;
    Digest outfile_digest_a;
    Digest stdout_digest_a;
    Digest stderr_digest_a;
    UpdateActionResultRequest update_request_a;
    GetActionResultRequest get_request_a;

    void SetUp() override
    {
        // Test ActionResult "A" to be used by test
        outfile_digest_a = DigestGenerator::hash("testa");
        stdout_digest_a = DigestGenerator::hash("stdouta");
        stderr_digest_a = DigestGenerator::hash("stderra");

        action_result_a.set_exit_code(static_cast<google::protobuf::int32>(8));
        action_result_a.mutable_stdout_digest()->CopyFrom(stdout_digest_a);
        action_result_a.mutable_stderr_digest()->CopyFrom(stderr_digest_a);
        OutputFile outfile_a;
        outfile_a.mutable_digest()->CopyFrom(outfile_digest_a);
        *action_result_a.add_output_files() = outfile_a;

        action_digest_a = DigestGenerator::hash("testa");
        update_request_a.mutable_action_digest()->CopyFrom(action_digest_a);
        update_request_a.mutable_action_result()->CopyFrom(action_result_a);
        get_request_a.mutable_action_digest()->CopyFrom(action_digest_a);
    }

    ProxyParamFixture(const ProxyParamFixture &other) = delete;
    ProxyParamFixture(ProxyParamFixture &&other) = delete;
    ProxyParamFixture &operator=(const ProxyParamFixture &other) = delete;
    ProxyParamFixture &operator=(ProxyParamFixture &&other) = delete;

  public:
    ~ProxyParamFixture() override {}
};

TEST_P(ProxyParamFixture, GetInCacheActionResult)
{
    ActionResult result_buffer = {};

    // Given: Data in local storage
    local_storage->writeBlob(outfile_digest_a, "testa");
    local_storage->writeBlob(stdout_digest_a, "stdouta");
    local_storage->writeBlob(stderr_digest_a, "stderra");

    // When: Update ActionResult to local server
    ASSERT_TRUE(ac_servicer
                    ->UpdateActionResult(&server_context, &update_request_a,
                                         &result_buffer)
                    .ok());
    EXPECT_EQ(result_buffer.exit_code(), action_result_a.exit_code());
    EXPECT_EQ(result_buffer.stdout_digest().hash(),
              action_result_a.stdout_digest().hash());

    // Then: Fetching it back to confirm it's there
    ASSERT_TRUE(
        ac_servicer
            ->GetActionResult(&server_context, &get_request_a, &result_buffer)
            .ok());
    EXPECT_EQ(result_buffer.exit_code(), action_result_a.exit_code());
    EXPECT_EQ(result_buffer.stdout_digest().hash(),
              action_result_a.stdout_digest().hash());
}

TEST_P(ProxyParamFixture, GetNotInCacheActionResult)
{
    ActionResult result_buffer = {};

    // Given: Data in remote storage
    remote_storage->writeBlob(outfile_digest_a, "testa");
    remote_storage->writeBlob(stdout_digest_a, "stdouta");
    remote_storage->writeBlob(stderr_digest_a, "stderra");

    // When: Update ActionResult to remote server
    ASSERT_TRUE(remote_ac_servicer
                    ->UpdateActionResult(&server_context, &update_request_a,
                                         &result_buffer)
                    .ok());
    EXPECT_EQ(result_buffer.exit_code(), action_result_a.exit_code());
    EXPECT_EQ(result_buffer.stdout_digest().hash(),
              action_result_a.stdout_digest().hash());

    // Then: Fetching it from local will cause it to fetch from remote.
    ASSERT_TRUE(
        ac_servicer
            ->GetActionResult(&server_context, &get_request_a, &result_buffer)
            .ok());
    EXPECT_EQ(result_buffer.exit_code(), action_result_a.exit_code());
    EXPECT_EQ(result_buffer.stdout_digest().hash(),
              action_result_a.stdout_digest().hash());
}

TEST_P(ProxyParamFixture, NotPropagateActionResultToRemote)
{
    ActionResult result_buffer = {};

    // Given: Data in local and remote storage
    local_storage->writeBlob(outfile_digest_a, "testa");
    local_storage->writeBlob(stdout_digest_a, "stdouta");
    local_storage->writeBlob(stderr_digest_a, "stderra");
    remote_storage->writeBlob(outfile_digest_a, "testa");
    remote_storage->writeBlob(stdout_digest_a, "stdouta");
    remote_storage->writeBlob(stderr_digest_a, "stderra");

    // When: Update ActionResult to local server
    ASSERT_TRUE(ac_servicer
                    ->UpdateActionResult(&server_context, &update_request_a,
                                         &result_buffer)
                    .ok());
    EXPECT_EQ(result_buffer.exit_code(), action_result_a.exit_code());
    EXPECT_EQ(result_buffer.stdout_digest().hash(),
              action_result_a.stdout_digest().hash());

    // Then: ActionResult will NOT have propagated to remote server because
    // the proxy is in --read-only-remote mode
    ASSERT_FALSE(
        remote_ac_servicer
            ->GetActionResult(&server_context, &get_request_a, &result_buffer)
            .ok());
}

INSTANTIATE_TEST_SUITE_P(CasProxyModeParam, ProxyParamFixture,
                         testing::Values(ENABLED));
