# -*- coding: utf-8 -*-
require "spec_helper"

require "socket"

unless ENV["CI"]
  CERTIFICATE_DIR = ENV.fetch("BUNNY_CERTIFICATE_DIR", "./spec/tls")
  puts "Will use certificates from #{CERTIFICATE_DIR}"

  shared_examples_for "successful TLS connection" do
    it "succeeds" do
      expect(subject).to be_tls
      ch = subject.create_channel
      ch.confirm_select

      q  = ch.queue("", exclusive: true)
      x  = ch.default_exchange

      x.publish("xyzzy", routing_key: q.name).
        publish("xyzzy", routing_key: q.name).
        publish("xyzzy", routing_key: q.name).
        publish("xyzzy", routing_key: q.name)

      x.wait_for_confirms
      expect(q.message_count).to eq 4

      i = 0
      q.subscribe do |delivery_info, _, payload|
        i += 1
      end
      sleep 1.0
      expect(i).to eq 4
      expect(q.message_count).to eq 0

      ch.close
    end
  end

  def local_hostname
    ENV.fetch("BUNNY_RABBITMQ_HOSTNAME", "localhost")
  end

  context "initialized with :tls => true" do
    let(:subject) do
      Bunny.new(
        hostname:  local_hostname(),
        user:     "bunny_gem",
        password: "bunny_password",
        vhost:    "bunny_testbed",
        tls: true,
        verify_peer: verify_peer,
        tls_cert: "#{CERTIFICATE_DIR}/client_certificate.pem",
        tls_key: "#{CERTIFICATE_DIR}/client_key.pem",
        tls_ca_certificates: ["#{CERTIFICATE_DIR}/ca_certificate.pem"])
    end

    context "peer verification is off" do
      let(:verify_peer) { false }

      it "uses TLS port" do
        expect(subject.port).to eq AMQ::Protocol::TLS_PORT
      end

      it "sends the SNI details" do
        # https://github.com/ruby-amqp/bunny/issues/440
        subject.start
        expect(subject.transport.socket.hostname).to_not be_empty
      end

      after :each do
        subject.close
      end
    end

    context "peer verification is on" do
      let(:verify_peer) { true }

      it "uses TLS port" do
        expect(subject.port).to eq AMQ::Protocol::TLS_PORT
      end
    end
  end

  describe "TLS connection to RabbitMQ with client certificates" do
    let(:subject) do
      c = Bunny.new(
        hostname: local_hostname(),
        username: "bunny_gem",
        password: "bunny_password",
        vhost: "bunny_testbed",
        tls: true,
        tls_cert: "#{CERTIFICATE_DIR}/client_certificate.pem",
        tls_key: "#{CERTIFICATE_DIR}/client_key.pem",
        tls_ca_certificates: ["#{CERTIFICATE_DIR}/ca_certificate.pem"],
        verify_peer: false)
      c.start
      c
    end

    after :each do
      subject.close
    end

    include_examples "successful TLS connection"
  end


  describe "TLS connection to RabbitMQ without client certificates" do
    let(:subject) do
      c = Bunny.new(
        hostname: local_hostname(),
        username: "bunny_gem",
        password: "bunny_password",
        vhost: "bunny_testbed",
        tls: true,
        tls_ca_certificates: ["#{CERTIFICATE_DIR}/ca_certificate.pem"],
        verify_peer: false)
      c.start
      c
    end

    after :each do
      subject.close
    end

    include_examples "successful TLS connection"
  end


  describe "TLS connection to RabbitMQ with a connection string" do
    let(:subject) do
      c = Bunny.new("amqps://bunny_gem:bunny_password@#{local_hostname()}/bunny_testbed",
        tls_cert: "#{CERTIFICATE_DIR}/client_certificate.pem",
        tls_key: "#{CERTIFICATE_DIR}/client_key.pem",
        tls_ca_certificates: ["#{CERTIFICATE_DIR}/ca_certificate.pem"],
        verify_peer: false)
      c.start
      c
    end

    after :each do
      subject.close
    end

    include_examples "successful TLS connection"

    context "when URI contains query parameters" do
      subject(:session) do
        Bunny.new("amqps://bunny_gem:bunny_password@#{local_hostname()}/bunny_testbed?heartbeat=10&connection_timeout=100&channel_max=1000&verify=false&cacertfile=#{CERTIFICATE_DIR}/ca_certificate.pem&certfile=#{CERTIFICATE_DIR}/client_certificate.pem&keyfile=#{CERTIFICATE_DIR}/client_key.pem")
      end

      it "parses extra connection parameters" do
        session.start

        expect(session.uses_tls?).to eq(true)
        expect(session.transport.verify_peer).to eq(false)
        expect(session.transport.tls_ca_certificates).to eq(["#{CERTIFICATE_DIR}/ca_certificate.pem"])
        expect(session.transport.tls_certificate_path).to eq("#{CERTIFICATE_DIR}/client_certificate.pem")
        expect(session.transport.tls_key_path).to eq("#{CERTIFICATE_DIR}/client_key.pem")
      end
    end
  end


  describe "TLS connection to RabbitMQ with a connection string and w/o client certificate and key" do
    let(:subject) do
      c = Bunny.new("amqps://bunny_gem:bunny_password@#{local_hostname()}/bunny_testbed",
        tls_ca_certificates: ["#{CERTIFICATE_DIR}/ca_certificate.pem"],
        verify_peer: verify_peer)
      c.start
      c
    end

    after :each do
      subject.close
    end

    context "peer verification is off" do
      let(:verify_peer) { false }

      include_examples "successful TLS connection"

      it "sends the SNI details" do
        # https://github.com/ruby-amqp/bunny/issues/440
        expect(subject.transport.socket.hostname).to_not be_empty
      end
    end

    context "peer verification is on" do
      let(:verify_peer) { true }

      include_examples "successful TLS connection"

      it "sends the SNI details" do
        # https://github.com/ruby-amqp/bunny/issues/440
        expect(subject.transport.socket.hostname).to_not be_empty
      end
    end
  end


  describe "TLS connection to RabbitMQ with client certificates provided inline" do
    let(:subject) do
      c = Bunny.new(
        hostname: local_hostname(),
        username: "bunny_gem",
        password: "bunny_password",
        vhost: "bunny_testbed",
        tls: true,
        tls_cert: File.read("#{CERTIFICATE_DIR}/client_certificate.pem"),
        tls_key: File.read("#{CERTIFICATE_DIR}/client_key.pem"),
        tls_ca_certificates: ["#{CERTIFICATE_DIR}/ca_certificate.pem"],
        verify_peer: false)
      c.start
      c
    end

    after :each do
      subject.close
    end

    include_examples "successful TLS connection"
  end

  describe "TLS connection to RabbitMQ with tls_version TLSv1.1 specified" do
    let(:subject) do
      c = Bunny.new(
        hostname: local_hostname(),
        username: "bunny_gem",
        password: "bunny_password",
        vhost: "bunny_testbed",
        tls: true,
        tls_protocol: :TLSv1_1,
        tls_ca_certificates: ["#{CERTIFICATE_DIR}/ca_certificate.pem"],
        verify_peer: false)
      c.start
      c
    end

    after :each do
      subject.close
    end

    include_examples "successful TLS connection"

    it "connects using TLSv1.1" do
      expect(subject.transport.socket.ssl_version).to eq "TLSv1.1"
    end
  end
end
