/*
 * 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 <chrono>
#include <cstdint>
#include <functional>
#include <optional>
#include <string>
#include <string_view>
#include <utility>

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

namespace snowflake::ingest::streaming {

/// RAII handle for an open Snowpipe Streaming channel.
///
/// Not copyable.  Move-constructible and move-assignable.
/// Obtained via client::open_channel().
///
/// Thread safety: all const and non-mutating operations (append_rows,
/// get_channel_status, initiate_flush, wait_for_flush, wait_for_commit,
/// 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 channel {
 public:
  ~channel();

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

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

  /// Append NDJSON-encoded rows to the channel.
  ///
  /// @param ndjson       Newline-delimited JSON; one JSON object per row.
  /// @param num_rows     Number of JSON objects in `ndjson`.
  /// @param start_offset_token  Optional start offset token.
  /// @param end_offset_token    Optional end offset token.
  result<void> append_rows(
      std::string_view ndjson, int64_t num_rows,
      std::optional<std::string_view> start_offset_token = std::nullopt,
      std::optional<std::string_view> end_offset_token = std::nullopt);

  /// Poll until the user-supplied token_checker is satisfied or a timeout
  /// expires.  Mirrors Java's waitForCommit and Python's wait_for_commit.
  ///
  /// token_checker receives the latest committed offset token on every poll
  /// and should return true when the desired token has been reached.  Tokens
  /// are committed in batches, so the checker typically performs a
  /// greater-than-or-equal comparison rather than an exact match.
  ///
  /// @param token_checker   Callable invoked each poll cycle.
  /// @param timeout         Maximum time to wait (0 = no timeout).
  /// @param poll_interval   Sleep between polls (default 1 s).
  ///
  /// Returns kChannelWaitForFlushTimeout on timeout, kInvalidChannelError
  /// if the channel becomes invalid before the token is committed.
  result<void> wait_for_commit(
      std::function<bool(const std::optional<std::string>&)> token_checker,
      std::chrono::milliseconds timeout = std::chrono::milliseconds(0),
      std::chrono::milliseconds poll_interval =
          std::chrono::milliseconds(1000)) const;

  /// Close the channel.  Pass wait_for_flush=true (default) to block until
  /// buffered data has been ingested.  timeout_secs=0 means no timeout.
  result<void> close(bool wait_for_flush = true, int64_t timeout_secs = 0);

  /// Returns the current channel status from the Rust core.
  result<channel_status> get_channel_status() const;

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

  /// Block until the channel has flushed.  timeout_secs=0 means no timeout.
  result<void> wait_for_flush(int64_t timeout_secs = 0);

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

  const std::string& channel_name() const noexcept { return channel_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 channel(const void* ptr, std::string channel_name,
                   std::string db_name, std::string schema_name,
                   std::string pipe_name);
  friend class client;

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

}  // namespace snowflake::ingest::streaming
