# frozen_string_literal: true

begin
  require "grpc"
  require "logging"

  # A test message
  class EchoMsg
    attr_reader :msg

    def initialize(msg: "")
      @msg = msg
    end

    def self.marshal(o)
      o.msg
    end

    def self.unmarshal(msg)
      EchoMsg.new(msg: msg)
    end
  end

  # a test service that checks the cert of its peer
  class TestService
    include GRPC::GenericService
    rpc :an_rpc, EchoMsg, EchoMsg
    rpc :a_cancellable_rpc, EchoMsg, EchoMsg
    rpc :a_client_streaming_rpc, stream(EchoMsg), EchoMsg
    rpc :a_server_streaming_rpc, EchoMsg, stream(EchoMsg)
    rpc :a_bidi_rpc, stream(EchoMsg), stream(EchoMsg)

    def check_peer_cert(call)
      # error_msg = "want:\n#{client_cert}\n\ngot:\n#{call.peer_cert}"
      # fail(error_msg) unless call.peer_cert == client_cert
    end

    def an_rpc(req, call)
      check_peer_cert(call)
      req
    end

    def a_cancellable_rpc(_req, call)
      check_peer_cert(call)
      raise GRPC::Cancelled, "dump"
    end

    def a_client_streaming_rpc(call)
      check_peer_cert(call)
      call.each_remote_read.each { |r| GRPC.logger.info(r) }
      EchoMsg.new(msg: "client stream pong")
    end

    def a_server_streaming_rpc(_, call)
      check_peer_cert(call)
      call.send_initial_metadata
      [EchoMsg.new(msg: "server stream pong"), EchoMsg.new(msg: "server stream pong")]
    end

    def a_bidi_rpc(requests, call)
      check_peer_cert(call)
      requests.each { |r| GRPC.logger.info(r) }
      call.send_initial_metadata
      [EchoMsg.new(msg: "bidi pong"), EchoMsg.new(msg: "bidi pong")]
    end
  end

  if ENV.key?("HTTPX_DEBUG")
    log_level = ENV["HTTPX_DEBUG"].to_i
    log_level = log_level > 1 ? :debug : :info

    module GRPC
      extend Logging.globally
    end
    Logging.logger.root.appenders = Logging.appenders.stdout
    Logging.logger.root.level = log_level
    Logging.logger["GRPC"].level = log_level
    Logging.logger["GRPC::ActiveCall"].level = log_level
    Logging.logger["GRPC::BidiCall"].level = log_level
  end

  module GRPCHelpers
    include ::GRPC::Core::StatusCodes
    include ::GRPC::Core::TimeConsts
    include ::GRPC::Core::CallOps

    private

    def teardown
      super
      if @grpc_server

        @grpc_server.shutdown_and_notify(from_relative_time(2))
        @grpc_server.close
        @grpc_server_th.join if @grpc_server_th
      end

      return unless @rpc_server && !@rpc_server.stopped?

      @rpc_server.stop
      @rpc_server_th.join
    end

    def grpc_plugin
      grpc = HTTPX.plugin(:grpc)

      grpc = grpc.with_channel_credentials(*channel_credentials_paths, hostname: "foo.test.google.fr") if origin.start_with?("https")

      grpc
    end

    def run_rpc(service, server_args: {})
      @rpc_server = ::GRPC::RpcServer.new(server_args: server_args.merge("grpc.so_reuseport" => 0))

      cred = origin.start_with?("https") ? server_credentials : :this_port_is_insecure

      server_port = @rpc_server.add_http2_port("localhost:0", cred)
      @rpc_server.handle(service)

      @rpc_server_th = Thread.new { @rpc_server.run }
      @rpc_server.wait_till_running

      server_port
    end

    def run_request_response(resp, status, marshal: nil, server_args: {}, server_initial_md: {}, server_trailing_md: {})
      @grpc_server = ::GRPC::Core::Server.new(server_args.merge("grpc.so_reuseport" => 0))

      cred = origin.start_with?("https") ? server_credentials : :this_port_is_insecure

      server_port = @grpc_server.add_http2_port("localhost:0", cred)

      @grpc_server_th = wakey_thread do |notifier|
        c = expect_server_to_be_invoked(notifier, metadata_to_send: server_initial_md, marshal: marshal)
        begin
          yield c
        ensure
          c.remote_send(resp)
          c.send_status(status, status == OK ? "OK" : "NOK", true, metadata: server_trailing_md)
          c.send(:set_input_stream_done)
          c.send(:set_output_stream_done)
        end
      end

      server_port
    end

    def wakey_thread(&blk)
      n = ::GRPC::Notifier.new
      t = Thread.new do
        begin
          blk.call(n)
        rescue GRPC::Core::CallError
        end
      end
      t.abort_on_exception = true
      n.wait
      t
    end

    def expect_server_to_be_invoked(notifier, metadata_to_send: nil, marshal: nil)
      @grpc_server.start
      notifier.notify(nil)
      recvd_rpc = @grpc_server.request_call
      recvd_call = recvd_rpc.call
      recvd_call.metadata = recvd_rpc.metadata
      recvd_call.run_batch(SEND_INITIAL_METADATA => metadata_to_send)
      ::GRPC::ActiveCall.new(recvd_call, marshal, marshal, INFINITE_FUTURE, metadata_received: true)
    end

    def server_credentials
      creds = ["ca.pem", "server1.key", "server1.pem"]
              .map { |path| File.join(grpc_testdata_path, path) }
              .map(&File.method(:read))

      GRPC::Core::ServerCredentials.new(
        creds[0],
        [{ private_key: creds[1], cert_chain: creds[2] }],
        true
      ) # force client auth
    end

    def channel_credentials_paths
      ["ca.pem", "client.key", "client.pem"]
        .map { |path| File.join(grpc_testdata_path, path) }
      #   .map(&File.method(:read))
      # GRPC::Core::ChannelCredentials.new(*creds)
    end

    def grpc_testdata_path
      grpc_path = Gem::Specification.find_by_path("grpc").full_gem_path
      File.join(grpc_path, "src", "ruby", "spec", "testdata")
    end
  end
rescue LoadError
  module GRPCHelpers
  end
end
