module ActiveMerchant # :nodoc:
  module Billing # :nodoc:
    class WompiGateway < Gateway
      version 'v1'

      self.test_url = "https://sync.sandbox.wompi.co/#{fetch_version}"
      self.live_url = "https://sync.production.wompi.co/#{fetch_version}"

      self.supported_countries = ['CO']
      self.default_currency = 'COP'
      self.supported_cardtypes = %i[visa master american_express]

      self.homepage_url = 'https://wompi.co/'
      self.display_name = 'Wompi'

      self.money_format = :cents

      def initialize(options = {})
        ## Sandbox keys have prefix pub_test_ and prv_test_
        ## Production keys have prefix pub_prod_ and prv_prod_
        begin
          requires!(options, :prod_private_key, :prod_public_key)
        rescue ArgumentError
          begin
            requires!(options, :test_private_key, :test_public_key)
          rescue ArgumentError
            raise ArgumentError, 'Gateway requires both test_private_key and test_public_key, or both prod_private_key and prod_public_key'
          end
        end
        super
      end

      def purchase(money, payment, options = {})
        post = {
          reference: options[:reference] || generate_reference,
          public_key:
        }
        add_invoice(post, money, options)
        add_tip_in_cents(post, options)
        add_card(post, payment, options)

        commit('sale', post, '/transactions_sync')
      end

      def authorize(money, payment, options = {})
        post = {
          public_key:,
          type: 'CARD',
          financial_operation: 'PREAUTHORIZATION'
        }
        add_auth_params(post, money, payment, options)

        commit('authorize', post, '/payment_sources_sync')
      end

      def capture(money, authorization, options = {})
        post = {
          reference: options[:reference] || generate_reference,
          public_key:,
          payment_source_id: authorization.to_i
        }
        add_invoice(post, money, options)
        commit('capture', post, '/transactions_sync')
      end

      def refund(money, authorization, options = {})
        # post = { amount_in_cents: amount(money).to_i, transaction_id: authorization.to_s }
        # commit('refund', post, '/refunds_sync')

        # All refunds will instead be voided. This is temporary.
        void(authorization, options, money)
      end

      def void(authorization, options = {}, money = nil)
        post = money ? { amount_in_cents: amount(money).to_i } : {}
        commit('void', post, "/transactions/#{authorization}/void_sync")
      end

      def supports_scrubbing?
        true
      end

      def scrub(transcript)
        transcript.gsub(/(Bearer )\w+/, '\1[REDACTED]').
          gsub(/(\\\"number\\\":\\\")\d+/, '\1[REDACTED]').
          gsub(/(\\\"cvc\\\":\\\")\d+/, '\1[REDACTED]').
          gsub(/(\\\"phone_number\\\":\\\")\+?\d+/, '\1[REDACTED]').
          gsub(/(\\\"email\\\":\\\")\S+\\\",/, '\1[REDACTED]\",').
          gsub(/(\\\"legal_id\\\":\\\")\d+/, '\1[REDACTED]')
      end

      private

      def headers
        {
          'Authorization' => "Bearer #{private_key}",
          'Content-Type' => 'application/json'
        }
      end

      def generate_reference
        SecureRandom.alphanumeric(12)
      end

      def private_key
        test? ? options[:test_private_key] : options[:prod_private_key]
      end

      def public_key
        test? ? options[:test_public_key] : options[:prod_public_key]
      end

      def add_invoice(post, money, options)
        post[:amount_in_cents] = amount(money).to_i
        post[:currency] = (options[:currency] || currency(money))
      end

      def add_card(post, card, options)
        payment_method = {
          type: 'CARD'
        }
        add_basic_card_info(payment_method, card, options)
        post[:payment_method] = payment_method
      end

      def add_auth_params(post, money, card, options)
        data = {
          amount_in_cents: amount(money).to_i,
          currency: (options[:currency] || currency(money))
        }
        add_basic_card_info(data, card, options)
        post[:data] = data
      end

      def add_basic_card_info(post, card, options)
        installments = options[:installments] ? options[:installments].to_i : 1
        cvc = card.verification_value || nil

        post[:number] = card.number
        post[:exp_month] = card.month.to_s.rjust(2, '0')
        post[:exp_year] = card.year.to_s[2..3]
        post[:installments] = installments
        post[:card_holder] = card.name
        post[:cvc] = cvc if cvc && !cvc.empty?
      end

      def add_tip_in_cents(post, options)
        post[:tip_in_cents] = options[:tip_in_cents].to_i if options[:tip_in_cents]
      end

      def parse(body)
        JSON.parse(body)
      end

      def commit(action, parameters, endpoint)
        url = (test? ? test_url : live_url) + endpoint
        response = parse(ssl_post(url, post_data(action, parameters), headers))
        Response.new(
          success_from(response),
          message_from(response),
          response,
          authorization: authorization_from(response),
          avs_result: nil,
          cvv_result: nil,
          test: test?,
          error_code: error_code_from(response)
        )
      end

      def handle_response(response)
        case response.code.to_i
        when 200...300, 401, 404, 422
          response.body
        else
          raise ResponseError.new(response)
        end
      end

      def success_from(response)
        success_statuses.include? response.dig('data', 'status')
      end

      def success_statuses
        %w(APPROVED AVAILABLE)
      end

      def message_from(response)
        response.dig('data', 'status_message') || response.dig('error', 'reason') || response.dig('error', 'messages').to_json
      end

      def authorization_from(response)
        response.dig('data', 'transaction_id') || response.dig('data', 'id') || response.dig('data', 'transaction', 'id')
      end

      def post_data(action, parameters = {})
        parameters.to_json
      end

      def error_code_from(response)
        response.dig('error', 'type') unless success_from(response)
      end
    end
  end
end
