/*
 * Copyright 2026 Snowflake Inc.
 * SPDX-License-Identifier: Apache-2.0
 *
 * 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.
 */

#pragma once

#include <cstdint>
#include <map>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <vector>

#include "snowflake/ingest/streaming/channel.hpp"
#include "snowflake/ingest/streaming/channel_status.hpp"
#include "snowflake/ingest/streaming/result.hpp"

namespace snowflake::ingest::streaming {

/// Configuration for constructing a client.
struct client_options {
  std::string client_name;
  std::string db_name;
  std::string schema_name;
  std::string pipe_name;

  /// Path to a JSON credentials file.  Mutually exclusive with properties_json.
  std::string profile_json;

  /// Inline JSON credentials string.  Mutually exclusive with profile_json.
  std::string properties_json;
};

/// RAII wrapper around a Snowpipe Streaming client.
///
/// Not copyable.  Move-constructible and move-assignable.
/// Obtain via client::create().
///
/// Thread safety: all non-mutating operations (open_channel, drop_channel,
/// get_channel_statuses, initiate_flush, wait_for_flush, is_closed) may be
/// called concurrently from multiple threads on the same object — the Rust core
/// serialises access internally.  Lifecycle operations (construction,
/// destruction, move) must not overlap with any other use of the same object.
class client {
 public:
  ~client();

  client(const client&) = delete;
  client& operator=(const client&) = delete;

  client(client&& other) noexcept;
  client& operator=(client&& other) noexcept;

  /// Create a new client.
  static result<client> create(const client_options& options);

  /// Open a channel on this client.
  ///
  /// Returns a (channel, channel_status) pair on success.
  /// offset_token is optional; pass std::nullopt to start from the latest.
  result<std::pair<channel, channel_status>> open_channel(
      std::string_view channel_name,
      std::optional<std::string_view> offset_token = std::nullopt);

  /// Close the client.  wait_for_flush=true (default) flushes all channels.
  /// timeout_secs=0 means no timeout.
  result<void> close(bool wait_for_flush = true, int64_t timeout_secs = 0);

  /// Server-side drop a channel by name (deletes its metadata on Snowflake).
  result<void> drop_channel(std::string_view channel_name);

  /// Returns the status of each named channel.
  result<std::map<std::string, channel_status>> get_channel_statuses(
      const std::vector<std::string>& channel_names) const;

  /// Kick off an asynchronous flush on all channels (non-blocking).
  result<void> initiate_flush();

  /// Block until all channels have flushed.  timeout_secs=0 means no timeout.
  result<void> wait_for_flush(int64_t timeout_secs = 0);

  /// Returns true if the client has been closed.
  bool is_closed() const;

  const std::string& client_name() const noexcept { return client_name_; }
  const std::string& db_name() const noexcept { return db_name_; }
  const std::string& schema_name() const noexcept { return schema_name_; }
  const std::string& pipe_name() const noexcept { return pipe_name_; }

 private:
  explicit client(const void* ptr, std::string client_name, std::string db_name,
                  std::string schema_name, std::string pipe_name);

  const void* ptr_;
  std::string client_name_;
  std::string db_name_;
  std::string schema_name_;
  std::string pipe_name_;
};

}  // namespace snowflake::ingest::streaming
