////////////////////////////////////////////////////////////////////////////
//
// Copyright 2022 Realm Inc.
//
// 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.
//
////////////////////////////////////////////////////////////////////////////

#ifndef REALM_AUDIT_HPP
#define REALM_AUDIT_HPP

#include <realm/util/functional.hpp>
#include <realm/util/optional.hpp>
#include <realm/util/terminate.hpp>

#include <memory>
#include <string>
#include <vector>

namespace realm {
class DB;
class AuditObjectSerializer;
class Obj;
class SyncUser;
class TableView;
class Timestamp;
struct ColKey;
struct RealmConfig;
struct SyncError;
struct VersionID;
namespace util {
class Logger;
}

struct AuditConfig {
    // User to open the audit Realms with. If null, the user used to open the
    // Realm being audited is used
    std::shared_ptr<SyncUser> audit_user;
    // Prefix added to the start of generated partition keys for audit Realms.
    std::string partition_value_prefix = "audit";
    // Object serializer instance for converting objects to JSON payloads. If
    // null, a default implementation is used.
    std::shared_ptr<AuditObjectSerializer> serializer;
    // Logger for audit events. If null, the sync logger from the Realm under
    // audit is used.
    std::shared_ptr<util::Logger> logger;
    // Error handler which is called if fatal sync errors occur on the sync
    // Realm. If null, an error is logged then abort() is called.
    std::function<void(SyncError)> sync_error_handler;
    // Metadata to attach to each audit event. Each key used must be a property
    // in the server-side schema for AuditEvent. This is not validated and will
    // result in a sync error if violated.
    std::vector<std::pair<std::string, std::string>> metadata;
    // Root directory to store audit Realms
    std::string base_file_path;
};

class AuditInterface {
public:
    virtual ~AuditInterface() = default;

    // Internal interface for recording auditable events. SDKs may need to call
    // record_read() if they do not go through Object; record_query() and
    // record_write() should be handled automatically
    virtual void record_query(VersionID, const TableView&) = 0;
    virtual void record_read(VersionID, const Obj& obj, const Obj& parent, ColKey col) = 0;
    virtual void prepare_for_write(VersionID old_version) = 0;
    virtual void record_write(VersionID old_version, VersionID new_version) = 0;

    // -- Audit functionality which should be exposed in the SDK

    // Update the metadata attached to subsequence audit events. Does not effect
    // the current audit scope if called while a scope is active.
    virtual void update_metadata(std::vector<std::pair<std::string, std::string>> new_metadata) = 0;

    // Begin an audit scope. The given `name` is stored in the activity field
    // of each generated event. Returns an id which must be used to either
    // commit or cancel the scope.
    virtual uint64_t begin_scope(std::string_view name) = 0;
    // End the scope with the given id and asynchronously save it to disk. The
    // optional completion function is called once it has been committed (or an
    // error ocurred while trying to do so).
    virtual void end_scope(uint64_t, util::UniqueFunction<void(std::exception_ptr)>&& completion = nullptr) = 0;
    // Cancel the scope with the given id, discarding all events generated.
    virtual void cancel_scope(uint64_t) = 0;
    // Check if the scope with the given id is currently active and can be
    // committed or cancelled.
    virtual bool is_scope_valid(uint64_t) = 0;
    // Record a custom audit event. Does not use the scope (and does not need to be inside a scope).
    virtual void record_event(std::string_view activity, util::Optional<std::string> event_type,
                              util::Optional<std::string> data,
                              util::UniqueFunction<void(std::exception_ptr)>&& completion) = 0;

    // -- Test helper functionality

    // Wait for all scopes to be written to disk. Does not wait for them to be
    // uploaded to the server.
    virtual void wait_for_completion() = 0;
    // Wait for there to be no more data to upload. This is not a precise check;
    // if more scopes are created while this is waiting they may or may not be
    // included in the wait.
    virtual void wait_for_uploads() = 0;
};

std::shared_ptr<AuditInterface> make_audit_context(std::shared_ptr<DB>, RealmConfig const& parent_config);

// Hooks for testing. Do not use outside of tests.
namespace audit_test_hooks {
void set_maximum_shard_size(int64_t max_size);
// Not thread-safe, so this must be called at a point when no audit contexts exist.
void set_clock(util::UniqueFunction<Timestamp()>&&);
} // namespace audit_test_hooks

#if !REALM_PLATFORM_APPLE
inline std::shared_ptr<AuditInterface> make_audit_context(std::shared_ptr<DB>, RealmConfig const&)
{
    REALM_TERMINATE("Audit not supported on this platform");
}
#endif

} // namespace realm

#endif // REALM_AUDIT_HPP
