#  Copyright 2023. 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.

# frozen_string_literal: true

require "couchbase/collection_options"
require "couchbase/binary_collection_options"

module Couchbase
  module Protostellar
    module ResponseConverter
      class KV
        def self.to_get_result(resp, options)
          Couchbase::Collection::GetResult.new do |res|
            res.transcoder = options.transcoder
            res.cas = resp.cas
            res.expiry = extract_expiry_time(resp) if options.respond_to?(:with_expiry) && options.with_expiry
            res.encoded = if resp.content_uncompressed == "null" && !options.projections.empty?
                            "{}"
                          else
                            resp.content_uncompressed
                          end
            res.flags = resp.content_flags
          end
        end

        def self.to_mutation_result(resp)
          Couchbase::Collection::MutationResult.new do |res|
            res.cas = resp.cas
            res.mutation_token = extract_mutation_token(resp)
          end
        end

        def self.to_exists_result(resp)
          Couchbase::Collection::ExistsResult.new do |res|
            res.cas = resp.cas
            res.exists = resp.result
          end
        end

        def self.to_lookup_in_result(resp, specs, options, request)
          Couchbase::Collection::LookupInResult.new do |res|
            res.cas = resp.cas
            res.transcoder = options.transcoder
            res.encoded = resp.specs.each_with_index.map do |s, idx|
              Couchbase::Collection::SubDocumentField.new do |f|
                # TODO: What to do with the status?
                error = s.status.nil? ? nil : ErrorHandling.convert_rpc_status(s.status, request)
                f.error = error unless error.nil?
                f.index = idx
                f.path = specs[idx].path
                if specs[idx].type == :exists
                  f.exists = s.content == "true"
                  f.value = s.content
                elsif s.content.empty?
                  f.value = nil
                  f.exists = false
                else
                  f.value = s.content
                  f.exists = true
                end
              end
            end
          end
        end

        def self.to_get_any_replica_result(resps, options)
          begin
            entry = resps.next
          rescue StopIteration
            raise Couchbase::Error::DocumentIrretrievable, "unable to get replica of the document"
          end
          Couchbase::Collection::GetReplicaResult.new do |res|
            res.transcoder = options.transcoder
            res.cas = entry.cas
            res.flags = entry.content_flags
            res.encoded = entry.content
            res.is_replica = entry.is_replica
          end
        end

        def self.to_get_all_replicas_result(resps, options)
          resps.map do |entry|
            Couchbase::Collection::GetReplicaResult.new do |res|
              res.transcoder = options.transcoder
              res.cas = entry.cas
              res.flags = entry.content_flags
              res.encoded = entry.content
              res.is_replica = entry.is_replica
            end
          end
        end

        def self.to_mutate_in_result(resp, specs, options)
          Couchbase::Collection::MutateInResult.new do |res|
            res.cas = resp.cas
            res.transcoder = options.transcoder
            res.deleted = nil # TODO: gRPC response has no deleted field
            res.mutation_token = extract_mutation_token(resp)
            res.encoded = resp.specs.each_with_index.map do |s, idx|
              Couchbase::Collection::SubDocumentField.new do |f|
                f.index = idx
                f.path = specs[idx].path
                f.value = s.content
              end
            end
          end
        end

        def self.to_counter_result(resp)
          Couchbase::BinaryCollection::CounterResult.new do |res|
            res.cas = resp.cas
            res.content = resp.content
            res.mutation_token = extract_mutation_token(resp)
          end
        end

        def self.extract_mutation_token(resp)
          proto_token = resp.mutation_token
          return nil if proto_token.nil?

          Couchbase::MutationToken.new do |token|
            token.bucket_name = proto_token.bucket_name
            token.partition_id = proto_token.vbucket_id
            token.partition_uuid = proto_token.vbucket_uuid
            token.sequence_number = proto_token.seq_no
          end
        end

        def self.extract_expiry_time(resp)
          timestamp = resp.expiry

          return nil if timestamp.nil?

          Time.at(timestamp.seconds, timestamp.nanos, :nsec)
        end
      end
    end
  end
end
