/* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/*
 *   Copyright 2026. Couchbase, 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.
 */

#pragma once

#include <couchbase/analytics_meta_data.hxx>
#include <couchbase/analytics_stream_result.hxx>
#include <couchbase/error_codes.hxx>

#include "core/analytics_stream.hxx"

#include "core/error_context/analytics.hxx"
#include <atomic>
#include <future>

#include <memory>
#include <mutex>
#include <optional>
#include <system_error>
#include <utility>
#include <vector>

namespace couchbase::core::impl
{
class observability_recorder;
} // namespace couchbase::core::impl

namespace couchbase
{
class internal_analytics_stream_result
  : public std::enable_shared_from_this<internal_analytics_stream_result>
{
public:
  internal_analytics_stream_result(core::analytics_stream stream,
                                   std::unique_ptr<core::impl::observability_recorder> obs_rec,
                                   core::error_context::analytics ctx);
  // Non-copyable and non-movable: holds a std::mutex and is only ever owned via shared_ptr.
  internal_analytics_stream_result(const internal_analytics_stream_result&) = delete;
  internal_analytics_stream_result(internal_analytics_stream_result&&) = delete;
  auto operator=(const internal_analytics_stream_result&)
    -> internal_analytics_stream_result& = delete;
  auto operator=(internal_analytics_stream_result&&) -> internal_analytics_stream_result& = delete;

  ~internal_analytics_stream_result();

  void next(analytics_row_handler&& handler);
  void cancel();

  /**
   * Returns the analytics signature captured from the response metadata.
   */
  [[nodiscard]] auto signature() const -> std::optional<codec::binary>;

  /**
   * Returns a future that resolves to analytics metadata once the stream reaches its terminal
   * state (all rows consumed or an error). If the terminal has already been reached, returns a
   * ready future built from the stored value.
   */
  auto meta_data() -> std::future<std::pair<error, analytics_meta_data>>;

private:
  /**
   * Called when next_row reports the terminal condition (nullopt row).
   * Resolves the meta_data promise from the core stream.
   *
   * @param ec error code from the terminal next_row call (falsy = clean end)
   */
  void resolve_meta_data(std::error_code ec);

  core::analytics_stream stream_;

  // The request's error context as of a successfully started stream (statement, parameters, HTTP
  // status, endpoint). A terminal error is reported against a copy of this with the trailer's
  // diagnostics stamped on, so a mid-stream failure is as diagnosable as one that failed to start.
  core::error_context::analytics ctx_;

  // Observability recorder for the whole streaming operation. Its operation span already parents
  // the request; finish() is called exactly once from resolve_meta_data() (the single terminal
  // hook, reached on drain/error/cancel/destruction), recording the operation latency metric and
  // ending the span — the same instrumentation buffered analytics_query() emits via its recorder.
  std::unique_ptr<core::impl::observability_recorder> obs_rec_;

  // Guards the "only one outstanding next()" contract. Set while a pull is in flight and cleared
  // when its handler runs; a concurrent/overlapping next() is rejected with invalid_argument
  // rather than racing the single-consumer channel. Shared across handle copies (they alias one
  // stream), so copies cannot smuggle in a second concurrent pull either.
  std::atomic_bool pull_in_flight_{ false };

  mutable std::mutex meta_mutex_{};
  bool terminal_reached_{ false };
  std::pair<error, analytics_meta_data> terminal_value_{};
  // meta_data() may be called any number of times before the stream terminates; each call parks a
  // promise here and they are all satisfied together at the terminal. (A single shared promise
  // would throw future_already_retrieved on the second call.)
  std::vector<std::promise<std::pair<error, analytics_meta_data>>> pending_meta_promises_{};
};
} // namespace couchbase
