# frozen_string_literal: true

module ShotgunApiRuby
  class Auth < Faraday::Middleware
    module Validator
      def self.valid?(auth)
        (auth[:client_id] && auth[:client_secret]) ||
          (auth[:password] && auth[:username]) ||
          auth[:session_token] ||
          auth[:refresh_token]
      end
    end

    def initialize(app = nil, options = {})
      raise "missing auth" unless options[:auth]
      raise "missing site_url" unless options[:site_url]
      raise "Auth not valid" unless Validator.valid?(options[:auth])

      super(app)

      @site_url = options[:site_url]
      @client_id = options[:auth][:client_id]
      @client_secret = options[:auth][:client_secret]
      @username = options[:auth][:username]
      @password = options[:auth][:password]
      @session_token = options[:auth][:session_token]
      @refresh_token = options[:auth][:refresh_token]
    end

    attr_reader :client_id, :client_secret, :site_url, :username, :password, :session_token, :refresh_token

    def auth_type
      @auth_type ||=
        begin
          if client_id
            'client_credentials'
          elsif username
            'password'
          elsif session_token
            'session_token'
          elsif refresh_token
            'refresh_token'
          end
        end
    end

    def call(request_env)
      request_env[:request_headers].merge!(std_headers)

      @app.call(request_env)
    end

    private

    def auth_params
      @auth_params ||=
        begin
          case auth_type
          when 'client_credentials'
            "client_id=#{client_id}&client_secret=#{client_secret}&grant_type=client_credentials"
          when 'password'
            "username=#{username}&password=#{password}&grant_type=password"
          when 'session_token'
            "session_token=#{session_token}&grant_type=session_token"
          when 'refresh_token'
            "refresh_token=#{refresh_token}&grant_type=refresh_token"
          else
            raise "Not a valid/implemented auth type"
          end
        end
    end

    def auth_url
      @auth_url ||= "#{site_url}/auth/access_token?#{auth_params}"
    end

    def access_token
      ((@access_token && Time.now < @token_expiry) || sign_in) && @access_token
    end

    def sign_in
      resp =
        Faraday.post(auth_url) do |req|
          req.headers["Content-Type"] = 'application/x-www-form-urlencoded'
          req.headers["Accept"] = "application/json"
        end
      resp_body = JSON.parse(resp.body)

      raise "Can't login: #{resp_body['errors']}" if resp.status >= 300

      @access_token = resp_body['access_token']
      @token_expiry = Time.now + resp_body['expires_in']
      @refresh_token = resp_body['refresh_token']
    end

    def std_headers
      {
        "Accept" => "application/json",
        "Authorization" => "Bearer #{access_token}",
      }
    end
  end
end