include Killbill::Plugin::ActiveMerchant
module Killbill #:nodoc:
  module Stripe #:nodoc:
    class PaymentPlugin < ::Killbill::Plugin::ActiveMerchant::PaymentPlugin

      def initialize
        gateway_builder = Proc.new do |config|
          ::ActiveMerchant::Billing::StripeGateway.new :login => config[:api_secret_key]
        end

        super(gateway_builder,
              :stripe,
              ::Killbill::Stripe::StripePaymentMethod,
              ::Killbill::Stripe::StripeTransaction,
              ::Killbill::Stripe::StripeResponse)
      end

      def on_event(event)
        # Require to deal with per tenant configuration invalidation
        super(event)
        #
        # Custom event logic could be added below...
        #
      end

      def authorize_payment(kb_account_id, kb_payment_id, kb_payment_transaction_id, kb_payment_method_id, amount, currency, properties, context)
        pm = @payment_method_model.from_kb_payment_method_id(kb_payment_method_id, context.tenant_id)

        options = {}
        populate_defaults(pm, amount, properties, context, options)

        properties = merge_properties(properties, options)
        super(kb_account_id, kb_payment_id, kb_payment_transaction_id, kb_payment_method_id, amount, currency, properties, context)
      end

      def capture_payment(kb_account_id, kb_payment_id, kb_payment_transaction_id, kb_payment_method_id, amount, currency, properties, context)
        # Pass extra parameters for the gateway here
        options = {}

        properties = merge_properties(properties, options)
        super(kb_account_id, kb_payment_id, kb_payment_transaction_id, kb_payment_method_id, amount, currency, properties, context)
      end

      def purchase_payment(kb_account_id, kb_payment_id, kb_payment_transaction_id, kb_payment_method_id, amount, currency, properties, context)
        pm = @payment_method_model.from_kb_payment_method_id(kb_payment_method_id, context.tenant_id)

        options = {}
        populate_defaults(pm, amount, properties, context, options)

        properties = merge_properties(properties, options)
        super(kb_account_id, kb_payment_id, kb_payment_transaction_id, kb_payment_method_id, amount, currency, properties, context)
      end

      def void_payment(kb_account_id, kb_payment_id, kb_payment_transaction_id, kb_payment_method_id, properties, context)
        # Pass extra parameters for the gateway here
        options = {}

        properties = merge_properties(properties, options)
        super(kb_account_id, kb_payment_id, kb_payment_transaction_id, kb_payment_method_id, properties, context)
      end

      def credit_payment(kb_account_id, kb_payment_id, kb_payment_transaction_id, kb_payment_method_id, amount, currency, properties, context)
        # Pass extra parameters for the gateway here
        options = {}

        properties = merge_properties(properties, options)
        super(kb_account_id, kb_payment_id, kb_payment_transaction_id, kb_payment_method_id, amount, currency, properties, context)
      end

      def refund_payment(kb_account_id, kb_payment_id, kb_payment_transaction_id, kb_payment_method_id, amount, currency, properties, context)
        # Pass extra parameters for the gateway here
        options = {}

        reverse_transfer = find_value_from_properties(properties, :reverse_transfer)
        options[:reverse_transfer] = ::Killbill::Plugin::ActiveMerchant::Utils.normalize(reverse_transfer)

        refund_application_fee = find_value_from_properties(properties, :refund_application_fee)
        options[:refund_application_fee] = ::Killbill::Plugin::ActiveMerchant::Utils.normalize(refund_application_fee)

        properties = merge_properties(properties, options)
        super(kb_account_id, kb_payment_id, kb_payment_transaction_id, kb_payment_method_id, amount, currency, properties, context)
      end

      def get_payment_info(kb_account_id, kb_payment_id, properties, context)
        # Pass extra parameters for the gateway here
        options = {}

        properties = merge_properties(properties, options)
        super(kb_account_id, kb_payment_id, properties, context)
      end

      def search_payments(search_key, offset, limit, properties, context)
        # Pass extra parameters for the gateway here
        options = {}

        properties = merge_properties(properties, options)
        super(search_key, offset, limit, properties, context)
      end

      def add_payment_method(kb_account_id, kb_payment_method_id, payment_method_props, set_default, properties, context)
        # Do we have a customer for that account already?
        stripe_customer_id = find_value_from_properties(payment_method_props.properties, :customer) || StripePaymentMethod.stripe_customer_id_from_kb_account_id(kb_account_id, context.tenant_id)
        email = find_value_from_properties(payment_method_props.properties, :email) || @kb_apis.account_user_api.get_account_by_id(kb_account_id, @kb_apis.create_context(context.tenant_id)).email

        # Pass extra parameters for the gateway here
        options = {
            :email => email,
            # This will either update the current customer if present, or create a new one
            :customer => stripe_customer_id,
            # Magic field, see also private_api.rb (works only when creating an account)
            :description => kb_account_id
        }

        properties = merge_properties(properties, options)
        super(kb_account_id, kb_payment_method_id, payment_method_props, set_default, properties, context)
      end

      def delete_payment_method(kb_account_id, kb_payment_method_id, properties, context)
        pm = StripePaymentMethod.from_kb_payment_method_id(kb_payment_method_id, context.tenant_id)

        # Pass extra parameters for the gateway here
        options = {
            :customer_id => pm.stripe_customer_id
        }

        properties = merge_properties(properties, options)
        super(kb_account_id, kb_payment_method_id, properties, context)
      end

      def get_payment_method_detail(kb_account_id, kb_payment_method_id, properties, context)
        # Pass extra parameters for the gateway here
        options = {}

        properties = merge_properties(properties, options)
        super(kb_account_id, kb_payment_method_id, properties, context)
      end

      def set_default_payment_method(kb_account_id, kb_payment_method_id, properties, context)
        pm                           = StripePaymentMethod.from_kb_payment_method_id(kb_payment_method_id, context.tenant_id)

        # Update the default payment method on the customer object
        options                      = properties_to_hash(properties)
        payment_processor_account_id = options[:payment_processor_account_id] || :default
        gateway                      = lookup_gateway(payment_processor_account_id, context.tenant_id)
        stripe_response              = gateway.update_customer(pm.stripe_customer_id, :default_card => pm.token)
        response, transaction        = save_response_and_transaction(stripe_response, :set_default_payment_method, kb_account_id, context.tenant_id, payment_processor_account_id)

        if response.success
          # TODO Update our records
        else
          raise response.message
        end
      end

      def get_payment_methods(kb_account_id, refresh_from_gateway, properties, context)
        # Pass extra parameters for the gateway here
        options = {}

        properties = merge_properties(properties, options)
        super(kb_account_id, refresh_from_gateway, properties, context)
      end

      def search_payment_methods(search_key, offset, limit, properties, context)
        # Pass extra parameters for the gateway here
        options = {}

        properties = merge_properties(properties, options)
        super(search_key, offset, limit, properties, context)
      end

      def reset_payment_methods(kb_account_id, payment_methods, properties, context)
        super
      end

      def build_form_descriptor(kb_account_id, descriptor_fields, properties, context)
        # Pass extra parameters for the gateway here
        options = {}
        properties = merge_properties(properties, options)

        # Add your custom static hidden tags here
        options = {
            #:token => config[:stripe][:token]
        }
        descriptor_fields = merge_properties(descriptor_fields, options)

        super(kb_account_id, descriptor_fields, properties, context)
      end

      def process_notification(notification_json, properties, context)
        notification = JSON.parse(notification_json)
        gw_response = ::ActiveMerchant::Billing::Response.new(true,
                                                              nil,
                                                              notification,
                                                              :test => !notification['livemode'],
                                                              :authorization => notification['request'],
                                                              :avs_result => nil,
                                                              :cvv_result => nil,
                                                              :emv_authorization => nil,
                                                              :error_code => nil)
        save_response_and_transaction(gw_response, "webhook.#{notification['type']}".to_sym, nil, context.tenant_id, nil)

        gw_notification = ::Killbill::Plugin::Model::GatewayNotification.new
        gw_notification.kb_payment_id = nil
        gw_notification.status = 200
        gw_notification.headers = {}
        gw_notification.properties = []
        gw_notification
      end

      private

      def before_gateways(kb_transaction, last_transaction, payment_source, amount_in_cents, currency, options, context)
        super(kb_transaction, last_transaction, payment_source, amount_in_cents, currency, options, context)
        options[:idempotency_key] ||= kb_transaction.external_key
      end

      def get_payment_source(kb_payment_method_id, properties, options, context)
        return nil if options[:customer_id]
        super(kb_payment_method_id, properties, options, context)
      end

      def populate_defaults(pm, amount, properties, context, options)
        options[:customer] ||= pm.stripe_customer_id
        options[:destination] ||= get_destination(properties, context)
        options[:application_fee] ||= get_application_fee(amount, properties) unless options[:destination].nil?
      end

      def get_destination(properties, context)
        stripe_account_id = find_value_from_properties(properties, :destination)
        if stripe_account_id.nil?
          config(context.tenant_id)[:stripe][:stripe_destination]
        elsif stripe_account_id =~ /[A-Fa-f0-9]{8}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{12}/
          # Stripe doesn't use UUIDs - assume the destination is a Kill Bill account id
          ::Killbill::Stripe::StripeResponse.managed_stripe_account_id_from_kb_account_id(stripe_account_id, context.tenant_id)
        else
          stripe_account_id
        end
      end

      def get_application_fee(amount, properties)
        fees_amount = find_value_from_properties(properties, :fees_amount)
        return fees_amount unless fees_amount.nil?

        fees_percent = find_value_from_properties(properties, :fees_percent)
        return (fees_percent * amount * 100).to_i unless fees_percent.nil?

        config(context.tenant_id)[:stripe][:fees_amount] || (config(context.tenant_id)[:stripe][:fees_percent].to_f * amount * 100)
      end
    end
  end
end