# frozen_string_literal: true

##
# The WebauthnListener Response class is used by the WebauthnListener to create
# responses to be sent to the Gem host. It creates a Net::HTTPResponse instance
# when initialized and can be converted to the appropriate format to be sent by a socket using `to_s`.
# Net::HTTPResponse instances cannot be directly sent over a socket.
#
# Types of response classes:
#   - OkResponse
#   - NoContentResponse
#   - BadRequestResponse
#   - NotFoundResponse
#   - MethodNotAllowedResponse
#
# Example usage:
#
#   server = TCPServer.new(0)
#   socket = server.accept
#
#   response = OkResponse.for("https://rubygems.example")
#   socket.print response.to_s
#   socket.close
#

class Gem::WebauthnListener
  class Response
    attr_reader :http_response

    def self.for(host)
      new(host)
    end

    def initialize(host)
      @host = host

      build_http_response
    end

    def to_s
      status_line = "HTTP/#{@http_response.http_version} #{@http_response.code} #{@http_response.message}\r\n"
      headers = @http_response.to_hash.map {|header, value| "#{header}: #{value.join(", ")}\r\n" }.join + "\r\n"
      body = @http_response.body ? "#{@http_response.body}\n" : ""

      status_line + headers + body
    end

    private

    # Must be implemented in subclasses
    def code
      raise NotImplementedError
    end

    def reason_phrase
      raise NotImplementedError
    end

    def body; end

    def build_http_response
      response_class = Net::HTTPResponse::CODE_TO_OBJ[code.to_s]
      @http_response = response_class.new("1.1", code, reason_phrase)
      @http_response.instance_variable_set(:@read, true)

      add_connection_header
      add_access_control_headers
      add_body
    end

    def add_connection_header
      @http_response["connection"] = "close"
    end

    def add_access_control_headers
      @http_response["access-control-allow-origin"] = @host
      @http_response["access-control-allow-methods"] = "POST"
      @http_response["access-control-allow-headers"] = %w[Content-Type Authorization x-csrf-token]
    end

    def add_body
      return unless body
      @http_response["content-type"] = "text/plain"
      @http_response["content-length"] = body.bytesize
      @http_response.instance_variable_set(:@body, body)
    end
  end

  class OkResponse < Response
    private

    def code
      200
    end

    def reason_phrase
      "OK"
    end

    def body
      "success"
    end
  end

  class NoContentResponse < Response
    private

    def code
      204
    end

    def reason_phrase
      "No Content"
    end
  end

  class BadRequestResponse < Response
    private

    def code
      400
    end

    def reason_phrase
      "Bad Request"
    end

    def body
      "missing code parameter"
    end
  end

  class NotFoundResponse < Response
    private

    def code
      404
    end

    def reason_phrase
      "Not Found"
    end
  end

  class MethodNotAllowedResponse < Response
    private

    def code
      405
    end

    def reason_phrase
      "Method Not Allowed"
    end

    def add_access_control_headers
      super
      @http_response["allow"] = %w[GET OPTIONS]
    end
  end
end
