# frozen_string_literal: true

module Spree
  class Address < ApplicationRecord
    include AddressDisplay

    searchable_attributes :firstname, :lastname, :phone, :full_name, :full_name_reversed,
                          :full_name_with_comma, :full_name_with_comma_reversed
    searchable_associations :country, :state

    belongs_to :country, class_name: "Spree::Country"
    belongs_to :state, class_name: "Spree::State", optional: true

    has_one :enterprise, dependent: :restrict_with_exception
    has_many :shipments, dependent: :restrict_with_exception

    validates :address1, :city, :phone, presence: true
    validates :company, presence: true, unless: -> { first_name.blank? || last_name.blank? }
    validates :firstname, :lastname, presence: true, if: -> do
      company.blank? || company == 'unused'
    end
    validates :zipcode, presence: true, if: :require_zipcode?

    validate :state_validate

    after_save :touch_enterprise

    alias_attribute :first_name, :firstname
    alias_attribute :last_name, :lastname
    delegate :name, to: :state, prefix: true, allow_nil: true

    ransacker :full_name, formatter: proc { |value| value.to_s } do |parent|
      Arel::Nodes::SqlLiteral.new(
        "CONCAT(#{parent.table_name}.firstname, ' ', #{parent.table_name}.lastname)"
      )
    end

    ransacker :full_name_reversed, formatter: proc { |value| value.to_s } do |parent|
      Arel::Nodes::SqlLiteral.new(
        "CONCAT(#{parent.table_name}.lastname, ' ', #{parent.table_name}.firstname)"
      )
    end

    ransacker :full_name_with_comma, formatter: proc { |value| value.to_s } do |parent|
      Arel::Nodes::SqlLiteral.new(
        "CONCAT(#{parent.table_name}.firstname, ', ', #{parent.table_name}.lastname)"
      )
    end

    ransacker :full_name_with_comma_reversed, formatter: proc { |value| value.to_s } do |parent|
      Arel::Nodes::SqlLiteral.new(
        "CONCAT(#{parent.table_name}.lastname, ', ', #{parent.table_name}.firstname)"
      )
    end

    def self.default
      new(country: DefaultCountry.country)
    end

    def full_name
      "#{firstname} #{lastname}".strip
    end

    def state_text
      state.try(:abbr) || state.try(:name) || state_name
    end

    def same_as?(other)
      return false if other.nil?

      attributes.except('id', 'updated_at', 'created_at') ==
        other.attributes.except('id', 'updated_at', 'created_at')
    end

    alias same_as same_as?

    def to_s
      "#{full_name}: #{address1}"
    end

    def clone
      self.class.new(attributes.except('id', 'updated_at', 'created_at'))
    end

    def ==(other)
      self_attrs = attributes
      other_attrs = other.respond_to?(:attributes) ? other.attributes : {}

      [self_attrs, other_attrs].each { |attrs|
        attrs.except!('id', 'created_at', 'updated_at', 'order_id')
      }

      self_attrs == other_attrs
    end

    def empty?
      attributes.except('id', 'created_at', 'updated_at', 'order_id', 'country_id').all? { |_, v|
        v.nil?
      }
    end

    # Generates an ActiveMerchant compatible address hash
    def active_merchant_hash
      {
        name: full_name,
        address1:,
        address2:,
        city:,
        state: state_text,
        zip: zipcode,
        country: country.try(:iso),
        phone:
      }
    end

    def full_address
      render_address([address1, address2, city, zipcode, state&.name])
    end

    def address_part1
      render_address([address1, address2])
    end

    def address_part2
      render_address([city, zipcode, state&.name])
    end

    def address_and_city
      [address1, address2, city].compact_blank.join(' ')
    end

    private

    def require_zipcode?
      true
    end

    def state_validate
      # Skip state validation without country (also required)
      # or when disabled by preference
      return if country.blank? || !Spree::Config[:address_requires_state]
      return unless country.states_required

      # Ensure associated state belongs to country
      if state.present?
        if state.country == country
          self.state_name = nil # not required as we have a valid state and country combo
        elsif state_name.present?
          self.state = nil
        else
          errors.add(:state, :invalid)
        end
      end

      # Ensure state_name belongs to country without states,
      #   or that it matches a predefined state name/abbr
      if state_name.present? && country.states.present?
        states = country.states.find_all_by_name_or_abbr(state_name)

        if states.size == 1
          self.state = states.first
          self.state_name = nil
        else
          errors.add(:state, :invalid)
        end
      end

      # ensure at least one state field is populated
      errors.add :state, :blank if state.blank? && state_name.blank?
    end

    def touch_enterprise
      return unless enterprise&.persisted?

      enterprise.touch
    end

    def render_address(parts)
      parts.compact_blank.join(', ')
    end
  end
end
