// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you 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.

#pragma once

#include "block_service/block_service_manager.h"

#include <dsn/dist/block_service.h>
#include <dsn/utility/filesystem.h>

namespace dsn {
namespace dist {
namespace block_service {

class block_file_mock : public block_file
{
public:
    block_file_mock(const std::string &_name, int64_t _size, const std::string &_md5)
        : block_file(_name),
          size(_size),
          md5(_md5),
          enable_write_fail(false),
          enable_read_fail(false),
          enable_upload_fail(false)
    {
    }

    virtual uint64_t get_size() { return static_cast<uint64_t>(size); }

    virtual const std::string &get_md5sum() { return md5; }

    virtual dsn::task_ptr write(const write_request &req,
                                dsn::task_code code,
                                const write_callback &cb,
                                dsn::task_tracker *tracker = nullptr)
    {
        write_response resp;
        if (enable_write_fail) {
            resp.err = ERR_MOCK_INTERNAL;
        } else {
            resp.err = ERR_OK;
            context = std::move(req.buffer);
            resp.written_size = context.length();
        }
        cb(resp);
        return task_ptr();
    }

    virtual dsn::task_ptr read(const read_request &req,
                               dsn::task_code code,
                               const read_callback &cb,
                               dsn::task_tracker *tracker = nullptr)
    {
        read_response resp;
        if (enable_read_fail) {
            resp.err = ERR_MOCK_INTERNAL;
        } else {
            resp.err = ERR_OK;
            if (size <= 0 && md5.empty()) {
                resp.buffer = blob();
            } else {
                resp.buffer = context;
            }
        }
        cb(resp);
        return task_ptr();
    }

    virtual dsn::task_ptr upload(const upload_request &req,
                                 dsn::task_code code,
                                 const upload_callback &cb,
                                 dsn::task_tracker *tracker = nullptr)
    {
        upload_response resp;
        if (enable_upload_fail) {
            resp.err = ERR_MOCK_INTERNAL;
        } else {
            resp.err = ERR_OK;
            // just return the file size
            resp.uploaded_size = size;
        }
        cb(resp);
        return task_ptr();
    }

    virtual dsn::task_ptr download(const download_request &req,
                                   dsn::task_code code,
                                   const download_callback &cb,
                                   dsn::task_tracker *tracker = nullptr)
    {
        download_response resp;
        resp.err = ERR_OK;
        resp.downloaded_size = size;
        cb(resp);
        return task_ptr();
    }

    // make file exist,
    void file_exist(const std::string &_md5, int64_t _size)
    {
        md5 = _md5;
        size = _size;
    }
    // make file not exist
    void clear_file_exist()
    {
        size = 0;
        md5.clear();
    }

    void set_context(const std::string &value)
    {
        auto len = value.length();
        std::shared_ptr<char> buf = utils::make_shared_array<char>(len);
        ::memcpy(buf.get(), value.c_str(), len);
        blob write_buf(std::move(buf), static_cast<unsigned int>(len));
        context = std::move(write_buf);
    }
    void clear_context() { context = blob(); }

public:
    int64_t size;
    std::string md5;
    blob context;
    bool enable_write_fail;
    bool enable_read_fail;
    bool enable_upload_fail;
};

class block_service_mock : public block_filesystem
{
public:
    block_service_mock()
        : block_filesystem(), enable_create_file_fail(false), enable_list_dir_fail(false)
    {
    }
    virtual error_code initialize(const std::vector<std::string> &args) { return ERR_OK; }

    virtual dsn::task_ptr list_dir(const ls_request &req,
                                   dsn::task_code code,
                                   const ls_callback &callback,
                                   dsn::task_tracker *tracker = nullptr)
    {
        ls_response resp;
        if (enable_list_dir_fail) {
            resp.err = ERR_MOCK_INTERNAL;
        } else {
            resp.err = ERR_OK;
            std::string dir_name = ::dsn::utils::filesystem::get_file_name(req.dir_name);
            if (dir_files.find(dir_name) != dir_files.end()) {
                resp.entries = std::make_shared<std::vector<ls_entry>>();
                (*resp.entries) = dir_files[dir_name];
            } else {
                resp.err = ERR_OBJECT_NOT_FOUND;
            }
        }
        callback(resp);
        return task_ptr();
    }

    virtual dsn::task_ptr create_file(const create_file_request &req,
                                      dsn::task_code code,
                                      const create_file_callback &cb,
                                      dsn::task_tracker *tracker = nullptr)
    {
        create_file_response resp;
        if (enable_create_file_fail) {
            resp.err = ERR_MOCK_INTERNAL;
        } else {
            resp.err = ERR_OK;
            auto it = files.find(req.file_name);
            if (it != files.end()) {
                resp.file_handle =
                    new block_file_mock(req.file_name, it->second.first, it->second.second);
            } else {
                resp.file_handle = new block_file_mock("", 0, "");
                std::cout << "regular_file is selected..." << std::endl;
            }
        }

        cb(resp);
        return task_ptr();
    }

    dsn::task_ptr remove_path(const remove_path_request &req,
                              dsn::task_code code,
                              const remove_path_callback &cb,
                              dsn::task_tracker *tracker)
    {
        return task_ptr();
    }

public:
    std::map<std::string, std::vector<ls_entry>> dir_files;
    std::map<std::string, std::pair<int64_t, std::string>> files;
    bool enable_create_file_fail;
    bool enable_list_dir_fail;
};

} // namespace block_service
} // namespace dist
} // namespace dsn
