# frozen_string_literal: true

module Spree
  class User < ApplicationRecord
    include SetUnusedAddressFields

    self.belongs_to_required_by_default = false
    self.ignored_columns += [:authentication_token]

    searchable_attributes :email

    devise :database_authenticatable, :registerable, :recoverable,
           :rememberable, :trackable, :validatable, :omniauthable,
           :encryptable, :confirmable,
           encryptor: 'authlogic_sha512', reconfirmable: true,
           omniauth_providers: [:openid_connect]

    has_many :orders, dependent: nil
    belongs_to :ship_address, class_name: 'Spree::Address'
    belongs_to :bill_address, class_name: 'Spree::Address'

    before_validation :set_login
    after_create :associate_customers, :associate_orders
    before_destroy :check_completed_orders

    scope :admin, -> { where(admin: true) }
    scope :confirmed, -> { where.not(confirmed_at: nil) }

    has_many :enterprise_roles, dependent: :destroy
    has_many :enterprises, through: :enterprise_roles
    has_many :owned_enterprises, class_name: 'Enterprise',
                                 foreign_key: :owner_id, inverse_of: :owner,
                                 dependent: :restrict_with_exception
    has_many :owned_groups, class_name: 'EnterpriseGroup',
                            foreign_key: :owner_id, inverse_of: :owner,
                            dependent: :restrict_with_exception
    has_many :customers, dependent: :destroy
    has_many :credit_cards, dependent: :destroy
    has_many :report_rendering_options, class_name: "::ReportRenderingOptions", dependent: :destroy
    has_many :webhook_endpoints, dependent: :destroy
    has_many :column_preferences, dependent: :destroy
    has_one :oidc_account, dependent: :destroy

    accepts_nested_attributes_for :enterprise_roles, allow_destroy: true
    accepts_nested_attributes_for :webhook_endpoints

    accepts_nested_attributes_for :bill_address
    accepts_nested_attributes_for :ship_address

    validates :email, 'valid_email_2/email': { mx: true }, if: :email_changed?
    validate :limit_owned_enterprises

    class DestroyWithOrdersError < StandardError; end

    def self.admin_created?
      User.admin.count > 0
    end

    # Send devise-based user emails asyncronously via ActiveJob
    # See: https://github.com/heartcombo/devise/tree/v3.5.10#activejob-integration
    def send_devise_notification(notification, *)
      devise_mailer.public_send(notification, self, *).deliver_later
    end

    def regenerate_reset_password_token
      set_reset_password_token
    end

    def generate_api_key
      self.spree_api_key = SecureRandom.hex(24)
    end

    def known_users
      if admin?
        Spree::User.where(nil)
      else
        Spree::User
          .includes(:enterprises)
          .references(:enterprises)
          .where("enterprises.id IN (SELECT enterprise_id FROM enterprise_roles WHERE user_id = ?)",
                 id)
      end
    end

    def build_enterprise_roles
      Enterprise.find_each do |enterprise|
        unless enterprise_roles.find_by enterprise_id: enterprise.id
          enterprise_roles.build(enterprise:)
        end
      end
    end

    def customer_of(enterprise)
      return nil unless enterprise

      customers.find_by(enterprise_id: enterprise)
    end

    # This is a Devise Confirmable callback that runs on email confirmation
    # It sends a welcome email after the user email is confirmed
    def after_confirmation
      return unless confirmed? && unconfirmed_email.nil? && !unconfirmed_email_changed?

      send_signup_confirmation
    end

    def send_signup_confirmation
      Spree::UserMailer.signup_confirmation(self).deliver_later
    end

    def associate_customers
      self.customers = Customer.where(email:)
    end

    def associate_orders
      Spree::Order.where(customer: customers).find_each do |order|
        order.associate_user!(self)
      end
    end

    def can_own_more_enterprises?
      owned_enterprises.reload.size < enterprise_limit
    end

    def default_card
      # Don't re-fetch associated cards from the DB if they're already eager-loaded
      if credit_cards.loaded?
        credit_cards.to_a.find(&:is_default)
      else
        credit_cards.where(is_default: true).first
      end
    end

    def last_incomplete_spree_order
      orders.incomplete.where(created_by_id: id).order('created_at DESC').first
    end

    def disabled
      disabled_at.present?
    end

    def disabled=(value)
      self.disabled_at = value == '1' ? Time.zone.now : nil
    end

    def affiliate_enterprises
      return [] unless Flipper.enabled?(:affiliate_sales_data, self)

      Enterprise.joins(:connected_apps).merge(ConnectedApps::AffiliateSalesData.ready)
    end

    # Users can manage orders if they have a sells own/any enterprise. or is admin
    def can_manage_orders?
      @can_manage_orders ||= (enterprises.pluck(:sells).intersect?(%w(own any)) or admin?)
    end

    # Users can manage line items in orders if they have producer enterprise and
    # any of order distributors allow them to edit their orders.
    def can_manage_line_items_in_orders?
      return @can_manage_line_items_in_orders if defined? @can_manage_line_items_in_orders

      @can_manage_line_items_in_orders =
        enterprises.any?(&:is_producer_only) &&
        Spree::Order.editable_by_producers(enterprises).exists?
    end

    def can_manage_line_items_in_orders_only?
      !can_manage_orders? && can_manage_line_items_in_orders?
    end

    protected

    def password_required?
      !persisted? || password.present? || password_confirmation.present?
    end

    private

    def check_completed_orders
      raise DestroyWithOrdersError if orders.complete.present?
    end

    def set_login
      # for now force login to be same as email, eventually we will make this configurable, etc.
      self.login ||= email if email
    end

    def limit_owned_enterprises
      return unless owned_enterprises.size > enterprise_limit

      errors.add(:owned_enterprises, I18n.t(:spree_user_enterprise_limit_error,
                                            email:,
                                            enterprise_limit:))
    end

    def remove_payments_in_checkout(enterprises)
      enterprises.each do |enterprise|
        enterprise.distributed_orders.each do |order|
          order.payments.keep_if { |payment| payment.state != "checkout" }
        end
      end
    end
  end
end
