# frozen_string_literal: true

require 'English'

module CustomFields
  module Types
    module Money
      module Field
        extend ActiveSupport::Concern

        included do
          # allow_currency_from_symbol means that the user is able
          # to provide another currency instead of the the default
          # e.g. default is 'EUR' and User sets '100.11 USD'
          field :default_currency
          field :allow_currency_from_symbol, type: 'Boolean', default: false

          before_validation :set_default

          protected

          def set_default
            self.default_currency ||= CustomFields.options[:default_currency]
          end

          def check_currency
            ::Money::Currency.find(self.default_currency)
          end
        end

        def money_to_recipe
          { 'default_currency' => self.default_currency,
            'allow_currency_from_symbol' => allow_currency_from_symbol }
        end

        def money_as_json(_options = {})
          money_to_recipe
        end
      end

      module Target
        extend ActiveSupport::Concern

        module ClassMethods
          # Adds a Money field
          #
          # uses the money gem (https://github.com/RubyMoney/money)
          #
          # @param [ Class ] klass The class to modify
          # @param [ Hash ] rule It contains the name of the field and if it is required or not
          #
          # if allow_currency_from_symbol is set the formatted_name_field will return the amount
          # and the currency

          def apply_money_custom_field(klass, rule)
            # the field names
            name = rule['name']
            names = {
              name: name.to_sym,
              cents_field: "#{name}_cents".to_sym,
              currency_field: "#{name}_currency".to_sym,
              formatted_name_field: "formatted_#{name}".to_sym,
              allow_currency_from_symbol: "#{name}_allow_currency_from_symbol".to_sym,
              default_currency: "#{name}_default_currency".to_sym
            }

            # fields
            klass.field names[:cents_field],    type: ::Integer, localize: false
            klass.field names[:currency_field], type: ::String,  localize: false

            # getters and setters
            klass.send(:define_method, name) { _get_money(names) }
            klass.send(:define_method, :"#{name}=") { |value| _set_money(value, names) }

            klass.send(:define_method, names[:formatted_name_field]) { _get_formatted_money(names) }
            klass.send(:define_method, :"#{names[:formatted_name_field]}=") { |value| _set_money(value, names) }

            klass.send(:define_method, names[:allow_currency_from_symbol]) { rule['allow_currency_from_symbol'] }
            klass.send(:define_method, names[:default_currency]) { rule['default_currency'] }

            # validations
            klass.validate { _check_money(names) } if rule['required']
            klass.validates_presence_of(names[:cents_field], names[:currency_field]) if rule['required']
            klass.validates_numericality_of names[:cents_field], only_integer: true, if: names[:cents_field]
          end

          def money_attribute_get(instance, name)
            if value = instance.send(:"formatted_#{name}")
              { name => value, "formatted_#{name}" => value }
            else
              {}
            end
          end

          def money_attribute_set(instance, name, attributes)
            return unless attributes.key?(name) || attributes.key?("formatted_#{name}")

            value = attributes[name] || attributes["formatted_#{name}"]
            instance.send(:"formatted_#{name}=", value)
          end
        end

        protected

        def _set_money_defaults(names)
          ::Monetize.assume_from_symbol = send(names[:allow_currency_from_symbol])
          ::Money.default_currency = send(names[:default_currency])
        end

        def _get_money(names)
          _set_money_defaults(names)
          ::Money.new(read_attribute(names[:cents_field]),
                      read_attribute(names[:currency_field]) || ::Money.default_currency)
        end

        def _check_money(names)
          raise ArgumentError, 'Unrecognized amount' if [nil, ''].include? read_attribute.names[:cents_field]

          _get_money(names)
        rescue StandardError
          errors.add(names[:name], $ERROR_INFO.to_s)
          false
        end

        def _set_money(_money, names)
          return if _money.blank?

          _set_money_defaults(names)
          money = _money.is_a?(Money) ? _money : ::Monetize.parse(_money)
          write_attribute(names[:cents_field], money.cents)
          write_attribute(names[:currency_field], money.currency.iso_code)
        rescue StandardError
          errors.add(names[:name], $ERROR_INFO.to_s)
        end

        def _get_formatted_money(names)
          _get_money(names).format(symbol: send(names[:allow_currency_from_symbol]),
                                   no_cents_if_whole: true)
        rescue StandardError
          nil
        end
      end
    end
  end
end
