/*
 * Copyright 2021 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_localacinstance.h>
#include <buildboxcasd_localcasinstance.h>
#include <buildboxcasd_proxymodefixture.h>
#include <buildboxcommon_casclient.h>
#include <buildboxcommon_digestgenerator.h>
#include <buildboxcommon_filestager.h>
#include <buildboxcommon_fileutils.h>
#include <buildboxcommon_fslocalcas.h>
#include <buildboxcommon_logging.h>
#include <buildboxcommon_lrulocalcas.h>
#include <buildboxcommon_temporarydirectory.h>

#include <cstdlib>
#include <fstream>
#include <gtest/gtest.h>
#include <unordered_map>

using namespace buildboxcasd;

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

class LocalAcInstanceTestFixture : public ::testing::Test {
  public:
    buildboxcommon::TemporaryDirectory dir;
    std::shared_ptr<FsLocalActionStorage> storage;
    std::shared_ptr<LocalCas> cas_storage;
    std::unique_ptr<LocalCas> fs_storage;
    std::unique_ptr<LocalAcInstance> instance;
    Digest digest_a;
    Digest digest_b;
    Digest digest_c;
    Digest digest_d;
    ActionResult actionresult_a;
    ActionResult actionresult_b;
    ActionResult actionresult_c;
    ActionResult actionresult_d;
    UpdateActionResultRequest update_request_a;
    UpdateActionResultRequest update_request_b;
    UpdateActionResultRequest update_request_c;
    UpdateActionResultRequest update_request_d;
    GetActionResultRequest get_request_a;
    GetActionResultRequest get_request_b;
    GetActionResultRequest get_request_c;
    GetActionResultRequest get_request_d;

    LocalAcInstanceTestFixture() : dir("casd-tests") {};

    void SetUp() override
    {
        std::string path = dir.strname();

        fs_storage = std::make_unique<FsLocalCas>(path, true);
        cas_storage = std::make_shared<LruLocalCas>(
            std::move(fs_storage), INT64_MAX / 2, INT64_MAX, 100);
        std::unordered_map<StageTreeRequest_StagingMode,
                           std::shared_ptr<FileStager>>
            stagers;
        auto cas_instance = std::make_shared<LocalCasInstance>(
            cas_storage, stagers, nullptr, nullptr, "instance name");

        storage = std::make_shared<FsLocalActionStorage>(path);

        instance = std::make_unique<LocalAcInstance>(storage, cas_instance,
                                                     false /* readOnly */);

        Digest outfile_digest_a = DigestGenerator::hash("testa");
        Digest stdout_digest_a = DigestGenerator::hash("stdouta");
        Digest stderr_digest_a = DigestGenerator::hash("stderra");
        Digest outfile_digest_b = DigestGenerator::hash("testb");
        Digest stdout_digest_b = DigestGenerator::hash("stdoutb");
        Digest stderr_digest_b = DigestGenerator::hash("stderrb");
        Digest outfile_digest_c = DigestGenerator::hash("testc");
        Digest stdout_digest_c = DigestGenerator::hash("stdoutc");
        Digest stderr_digest_c = DigestGenerator::hash("stderrc");
        Digest outfile_digest_d = DigestGenerator::hash("testd");
        Digest stdout_digest_d = DigestGenerator::hash("stdoutd");
        Digest stderr_digest_d = DigestGenerator::hash("stderrd");

        digest_a = DigestGenerator::hash("testa");
        digest_b = DigestGenerator::hash("testb");
        digest_c = DigestGenerator::hash("testc"); // not placed in CAS
        digest_d = DigestGenerator::hash("testd"); // stderr missing

        cas_storage->writeBlob(outfile_digest_a, "testa");
        cas_storage->writeBlob(stdout_digest_a, "stdouta");
        cas_storage->writeBlob(stderr_digest_a, "stderra");
        cas_storage->writeBlob(outfile_digest_b, "testb");
        cas_storage->writeBlob(stdout_digest_b, "stdoutb");
        cas_storage->writeBlob(stderr_digest_b, "stderrb");
        cas_storage->writeBlob(outfile_digest_d, "testd");
        cas_storage->writeBlob(stdout_digest_d, "stdoutd");

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

        actionresult_b.set_exit_code(static_cast<google::protobuf::int32>(3));
        actionresult_b.mutable_stdout_digest()->CopyFrom(stdout_digest_b);
        actionresult_b.mutable_stderr_digest()->CopyFrom(stderr_digest_b);
        OutputFile outfile_b;
        outfile_b.mutable_digest()->CopyFrom(outfile_digest_b);
        *actionresult_b.add_output_files() = outfile_b;

        actionresult_c.set_exit_code(static_cast<google::protobuf::int32>(5));
        actionresult_c.mutable_stdout_digest()->CopyFrom(stdout_digest_c);
        actionresult_c.mutable_stderr_digest()->CopyFrom(stderr_digest_c);
        OutputFile outfile_c;
        outfile_c.mutable_digest()->CopyFrom(outfile_digest_c);
        *actionresult_c.add_output_files() = outfile_c;

        actionresult_d.set_exit_code(static_cast<google::protobuf::int32>(1));
        actionresult_d.mutable_stdout_digest()->CopyFrom(stdout_digest_d);
        actionresult_d.mutable_stderr_digest()->CopyFrom(stderr_digest_d);
        OutputFile outfile_d;
        outfile_d.mutable_digest()->CopyFrom(outfile_digest_d);
        *actionresult_d.add_output_files() = outfile_d;

        update_request_a.mutable_action_digest()->CopyFrom(digest_a);
        update_request_a.mutable_action_result()->CopyFrom(actionresult_a);
        get_request_a.mutable_action_digest()->CopyFrom(digest_a);

        update_request_b.mutable_action_digest()->CopyFrom(digest_a);
        update_request_b.mutable_action_result()->CopyFrom(actionresult_b);
        get_request_b.mutable_action_digest()->CopyFrom(digest_b);

        update_request_c.mutable_action_digest()->CopyFrom(digest_c);
        update_request_c.mutable_action_result()->CopyFrom(actionresult_c);
        get_request_c.mutable_action_digest()->CopyFrom(digest_c);

        update_request_d.mutable_action_digest()->CopyFrom(digest_d);
        update_request_d.mutable_action_result()->CopyFrom(actionresult_d);
        get_request_d.mutable_action_digest()->CopyFrom(digest_d);
    }

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

  public:
    ~LocalAcInstanceTestFixture() override {}
};

TEST_F(LocalAcInstanceTestFixture, UpdateAndGetInCache)
{
    ActionResult result;

    ASSERT_TRUE(instance->UpdateActionResult(update_request_a, &result).ok());
    ASSERT_TRUE(result.exit_code() == actionresult_a.exit_code());
    ASSERT_TRUE(result.stdout_digest().hash() ==
                actionresult_a.stdout_digest().hash());

    ASSERT_TRUE(instance->GetActionResult(get_request_a, &result).ok());
    ASSERT_TRUE(result.exit_code() == actionresult_a.exit_code());
    ASSERT_TRUE(result.stdout_digest().hash() ==
                actionresult_a.stdout_digest().hash());
}

TEST_F(LocalAcInstanceTestFixture, GetNotInCache)
{
    ActionResult result;

    ASSERT_FALSE(instance->GetActionResult(get_request_b, &result).ok());
}

TEST_F(LocalAcInstanceTestFixture, GetInCacheNotCas)
{
    ActionResult result;

    ASSERT_TRUE(instance->UpdateActionResult(update_request_c, &result).ok());
    ASSERT_TRUE(result.exit_code() == actionresult_c.exit_code());
    ASSERT_TRUE(result.stdout_digest().hash() ==
                actionresult_c.stdout_digest().hash());

    ASSERT_FALSE(instance->GetActionResult(get_request_c, &result).ok());
}

TEST_F(LocalAcInstanceTestFixture, OneDigestMissingFromCas)
{
    ActionResult result;

    ASSERT_TRUE(instance->UpdateActionResult(update_request_d, &result).ok());
    ASSERT_TRUE(result.exit_code() == actionresult_d.exit_code());
    ASSERT_TRUE(result.stdout_digest().hash() ==
                actionresult_d.stdout_digest().hash());

    ASSERT_FALSE(instance->GetActionResult(get_request_d, &result).ok());
}
