require 'omniauth-oauth2'

module OmniAuth
  module Strategies
    class LinkedIn < OmniAuth::Strategies::OAuth2
      option :name, 'linkedin'

      option :client_options, {
        :site => 'https://api.linkedin.com',
        :authorize_url => 'https://www.linkedin.com/oauth/v2/authorization?response_type=code',
        :token_url => 'https://www.linkedin.com/oauth/v2/accessToken'
      }

      option :scope, 'r_liteprofile r_emailaddress'
      option :fields, ['id', 'first-name', 'last-name', 'picture-url', 'email-address']

      uid do
        raw_info['id']
      end

      info do
        {
          :email => email_address,
          :first_name => localized_field('firstName'),
          :last_name => localized_field('lastName'),
          :picture_url => picture_url
        }
      end

      extra do
        {
          'raw_info' => raw_info
        }
      end

      def callback_url
        full_host + script_name + callback_path
      end

      alias :oauth2_access_token :access_token

      def access_token
        ::OAuth2::AccessToken.new(client, oauth2_access_token.token, {
          :expires_in => oauth2_access_token.expires_in,
          :expires_at => oauth2_access_token.expires_at,
          :refresh_token => oauth2_access_token.refresh_token
        })
      end

      def raw_info
        @raw_info ||= access_token.get(profile_endpoint).parsed
      end

      private

      def email_address
        if options.fields.include? 'email-address'
          fetch_email_address
          parse_email_address
        end
      end

      def fetch_email_address
        @email_address_response ||= access_token.get(email_address_endpoint).parsed
      end

      def parse_email_address
        return unless email_address_available?

        @email_address_response['elements'].first['handle~']['emailAddress']
      end

      def email_address_available?
        @email_address_response['elements'] &&
          @email_address_response['elements'].is_a?(Array) &&
          @email_address_response['elements'].first &&
          @email_address_response['elements'].first['handle~']
      end

      def fields_mapping
        {
          'id' => 'id',
          'first-name' => 'firstName',
          'last-name' => 'lastName',
          'picture-url' => 'profilePicture(displayImage~:playableStreams)'
        }
      end

      def fields
        options.fields.each.with_object([]) do |field, result|
          result << fields_mapping[field] if fields_mapping.has_key? field
        end
      end

      def localized_field field_name
        return unless localized_field_available? field_name

        raw_info[field_name]['localized'][field_locale(field_name)]
      end

      def field_locale field_name
        "#{ raw_info[field_name]['preferredLocale']['language'] }_" \
          "#{ raw_info[field_name]['preferredLocale']['country'] }"
      end

      def localized_field_available? field_name
        raw_info[field_name] && raw_info[field_name]['localized']
      end

      def picture_url
        return unless picture_available?

        picture_references.last['identifiers'].first['identifier']
      end

      def picture_available?
        raw_info['profilePicture'] &&
          raw_info['profilePicture']['displayImage~'] &&
          picture_references
      end

      def picture_references
        raw_info['profilePicture']['displayImage~']['elements']
      end

      def email_address_endpoint
        '/v2/emailAddress?q=members&projection=(elements*(handle~))'
      end

      def profile_endpoint
        "/v2/me?projection=(#{ fields.join(',') })"
      end
      
      def token_params
        super.tap do |params|
          params.client_secret = options.client_secret
        end
      end
    end
  end
end

OmniAuth.config.add_camelization 'linkedin', 'LinkedIn'
