# frozen_string_literal: true

require_relative "support/http_helpers"

class HTTPSTest < Minitest::Test
  include HTTPHelpers
  include Requests
  include Get
  include Head
  include WithBody
  include Headers
  include ResponseBody
  include IO
  include Errors if RUBY_ENGINE == "ruby"
  include Resolvers if ENV.key?("HTTPX_RESOLVER_URI")
  # TODO: uncomment as soon as nghttpx supports altsvc for HTTP/2
  # include AltSvc if ENV.key?("HTTPBIN_ALTSVC_HOST")

  include Plugins::Proxy unless ENV.key?("HTTPX_NO_PROXY")
  include Plugins::Authentication
  include Plugins::FollowRedirects
  include Plugins::Cookies
  include Plugins::Compression
  include Plugins::PushPromise if OpenSSL::SSL::SSLContext.instance_methods.include?(:alpn_protocols)
  include Plugins::Retries
  include Plugins::Multipart
  include Plugins::Expect
  include Plugins::RateLimiter
  include Plugins::Persistent unless RUBY_ENGINE == "jruby" || RUBY_VERSION < "2.3"
  include Plugins::Stream
  include Plugins::AWSAuthentication
  include Plugins::Upgrade
  include Plugins::GRPC if RUBY_ENGINE == "ruby" && RUBY_VERSION >= "2.3.0"
  include Plugins::ResponseCache

  def test_connection_coalescing
    coalesced_origin = "https://#{ENV["HTTPBIN_COALESCING_HOST"]}"
    HTTPX.plugin(SessionWithPool).wrap do |http|
      response1 = http.get(origin)
      verify_status(response1, 200)
      response2 = http.get(coalesced_origin)
      verify_status(response2, 200)
      # introspection time
      pool = http.pool
      connections = pool.connections
      origins = connections.map(&:origins)
      assert origins.any? { |orgs| orgs.sort == [origin, coalesced_origin].sort },
             "connections for #{[origin, coalesced_origin]} didn't coalesce (expected connection with both origins (#{origins}))"
    end
  end if ENV.key?("HTTPBIN_COALESCING_HOST")

  def test_verbose_log
    log = StringIO.new
    uri = build_uri("/get")
    response = HTTPX.get(uri, debug: log, debug_level: 2)
    verify_status(response, 200)
    log_output = log.string
    # assert tls output
    assert log_output.include?("SSL connection using TLSv")
    assert log_output.include?("ALPN, server accepted to use h2") unless RUBY_ENGINE == "jruby" || RUBY_VERSION < "2.3"
    assert log_output.include?("Server certificate:")
    assert log_output.include?(" subject: ")
    assert log_output.include?(" start date: ")
    assert log_output.include?(" expire date: ")
    assert log_output.include?(" issuer: ")
    assert log_output.include?(" SSL certificate verify ok")

    return if RUBY_ENGINE == "jruby" || RUBY_VERSION < "2.3"

    # assert request headers
    assert log_output.include?("HEADER: :scheme: https")
    assert log_output.include?("HEADER: :method: GET")
    assert log_output.include?("HEADER: :path: ")
    assert log_output.include?("HEADER: :authority: ")
    assert log_output.include?("HEADER: accept: */*")
    # assert response headers
    assert log_output.include?("HEADER: :status: 200")
    assert log_output.include?("HEADER: content-type: ")
    assert log_output.include?("HEADER: content-length: ")
  end

  unless RUBY_ENGINE == "jruby" || RUBY_VERSION < "2.3"
    # HTTP/2-specific tests

    def test_http2_max_streams
      uri = build_uri("/get")
      HTTPX.plugin(SessionWithSingleStream).plugin(SessionWithPool).wrap do |http|
        http.get(uri, uri)
        connection_count = http.pool.connection_count
        assert connection_count == 2, "expected to have 2 connections, instead have #{connection_count}"
        assert http.connection_exausted, "expected 1 connnection to have exhausted"
      end
    end

    def test_http2_uncoalesce_on_misdirected
      uri = build_uri("/status/421")
      HTTPX.plugin(SessionWithPool).wrap do |http|
        response = http.get(uri)
        verify_status(response, 421)
        connection_count = http.pool.connection_count
        assert connection_count == 2, "expected to have 2 connections, instead have #{connection_count}"
        assert response.version == "1.1", "request should have been retried with HTTP/1.1"
      end
    end

    def test_http2_settings_timeout
      uri = build_uri("/get")
      HTTPX.plugin(SessionWithPool).plugin(SessionWithFrameDelay).wrap do |http|
        response = http.get(uri)
        verify_error_response(response, /settings_timeout/)
      end
    end

    def test_http2_request_trailers
      uri = build_uri("/post")

      HTTPX.wrap do |http|
        total_time = start_time = nil
        trailered = false
        request = http.build_request(:post, uri, body: %w[this is chunked])
        request.on(:headers) do |_written_request|
          start_time = HTTPX::Utils.now
        end
        request.on(:trailers) do |written_request|
          total_time = HTTPX::Utils.elapsed_time(start_time)
          written_request.trailers["x-time-spent"] = total_time
          trailered = true
        end
        response = http.request(request)
        verify_status(response, 200)
        body = json_body(response)
        # verify_header(body["headers"], "x-time-spent", total_time.to_s)
        assert body.key?("data")
        assert trailered, "trailer callback wasn't called"
      end
    end

    def test_http2_client_sends_settings_timeout
      server = SettingsTimeoutServer.new
      th = Thread.new { server.accept }
      begin
        uri = "#{server.origin}/"
        http = HTTPX.plugin(SessionWithPool).with(timeout: { settings_timeout: 3 }, ssl: { verify_mode: OpenSSL::SSL::VERIFY_NONE })
        response = http.get(uri)
        verify_error_response(response, HTTPX::SettingsTimeoutError)
      ensure
        server.shutdown
        th.join
      end
      last_frame = server.frames.last
      assert last_frame[:error] == :settings_timeout
    end

    def test_http2_client_goaway_with_no_response
      server = KeepAlivePongServer.new
      th = Thread.new { server.accept }
      begin
        uri = "#{server.origin}/"
        HTTPX.plugin(SessionWithPool).with(ssl: { verify_mode: OpenSSL::SSL::VERIFY_NONE }) do |http|
          response = http.get(uri)
          verify_status(response, 200)
          response = http.get(uri)
          verify_error_response(response, HTTPX::Connection::HTTP2::GoawayError)
        end
      ensure
        server.shutdown
        th.join
      end
    end
  end

  def test_ssl_wrong_hostname
    uri = build_uri("/get")
    response = HTTPX.with(ssl: { hostname: "great-gatsby.com" }).get(uri)
    verify_error_response(response, /certificate verify failed|does not match the server certificate/)
  end

  private

  def origin(orig = httpbin)
    "https://#{orig}"
  end
end
