module Workarea
  class Payment
    module CreditCard
      extend ActiveSupport::Concern

      included do
        field :issuer, type: String
        field :display_number,type: String
        field :month, type: Integer
        field :year, type: Integer
        field :token, type: String

        attr_reader :number, :cvv
        attr_writer :cvv

        validate :number_presence
        validate :valid_number
        validate :cvv_presence
        validate :not_expired
        validate :issuer_accepted

        validates :first_name, presence: true
        validates :last_name, presence: true
        validates :cvv, numericality: { only_integer: true, allow_blank: true }
        validates :year, presence: true, numericality: { only_integer: true }
        validates :month, presence: true, numericality: {
          greater_than_or_equal_to: 1,
          less_than_or_equal_to: 12,
          only_integer: true
        }

        before_validation :set_display_number
        before_validation :set_issuer

        delegate :email, to: :profile
      end

      def number=(val)
        @number = val.to_s.gsub(/\D/, '')
      end

      def expired?
        Time.current > Time.zone.parse("#{month}/#{year}").next_month.beginning_of_month
      end

      def number_changed?
        number.present? && number.to_s.last(4) != display_number.to_s.last(4)
      end

      def card_change?
        persisted? && (number_changed? || month_changed? || year_changed?)
      end

      def tokenized?
        token.present?
      end

      def to_active_merchant
        ActiveMerchant::Billing::CreditCard.new(
          # TODO token doesn't make sense here because ActiveMerchant::CreditCard
          # strips anything that's not a number and most tokens are alpha numberic
          number: token.presence || number.presence || display_number,
          month: month,
          year: year,
          verification_value: cvv,
          first_name: first_name,
          last_name: last_name
        )
      end

      private

      def set_display_number
        if number.present?
          self.display_number = ActiveMerchant::Billing::CreditCard.mask(number)
        end
      end

      def set_issuer
        if number.present?
          brand = ActiveMerchant::Billing::CreditCard.brand?(number)

          if brand.present?
            self.issuer = Workarea.config.credit_card_issuers[brand].to_s
          end
        end
      end

      def issuer_accepted
        unless Workarea.config.credit_card_issuers.value?(issuer)
          message = I18n.t(
            'workarea.errors.messages.invalid_credit_card_issuer',
            value: issuer,
            issuers: Workarea.config.credit_card_issuers.values.to_sentence
          )
          errors.add(:base, message)
        end
      end

      def number_presence
        if !tokenized? && number.blank?
          errors.add(:number, I18n.t('errors.messages.blank'))
        end
      end

      def cvv_presence
        if (!tokenized? || card_change?) && cvv.blank?
          errors.add(:cvv, I18n.t('errors.messages.blank'))
        end
      end

      def valid_number
        if number.present? && !ActiveMerchant::Billing::CreditCard.valid_number?(number)
          errors.add(
            :number,
            I18n.t('workarea.errors.messages.invalid_credit_card_number')
          )
        end
      end

      def not_expired
        return unless month.present? && year.present?
        if expired?
          errors.add(
            :expiration_date,
            I18n.t('workarea.errors.messages.expired_date')
          )
        end
      end
    end
  end
end