# frozen_string_literal: true

module Spree
  module PromotionHandler
    class Coupon
      attr_reader :order, :coupon_code
      attr_accessor :error, :success, :status_code

      def initialize(order)
        @order = order
        @coupon_code = order.coupon_code && order.coupon_code.downcase
      end

      def can_apply?
        Spree::Promotion.order_activatable?(order)
      end

      def apply
        if coupon_code.present?
          if promotion.present? && promotion.active? && promotion.actions.exists?
            handle_present_promotion(promotion)
          elsif promotion_code&.promotion&.expired?
            set_error_code :coupon_code_expired
          else
            set_error_code :coupon_code_not_found
          end
        end

        self
      end

      def remove
        if promotion.blank?
          set_error_code :coupon_code_not_found
        elsif !promotion_exists_on_order?(order, promotion)
          set_error_code :coupon_code_not_present
        else
          promotion.remove_from(order)
          order.recalculate
          set_success_code :coupon_code_removed
        end

        self
      end

      def set_success_code(status_code)
        @status_code = status_code
        @success = I18n.t(status_code, scope: 'spree')
      end

      def set_error_code(status_code, options = {})
        @status_code = status_code
        @error = options[:error] || I18n.t(status_code, scope: 'spree')
      end

      def promotion
        @promotion ||= if promotion_code && promotion_code.promotion.active?
          promotion_code.promotion
        end
      end

      def successful?
        success.present? && error.blank?
      end

      private

      def promotion_code
        @promotion_code ||= Spree::PromotionCode.where(value: coupon_code).first
      end

      def handle_present_promotion(promotion)
        return promotion_usage_limit_exceeded if promotion.usage_limit_exceeded? || promotion_code.usage_limit_exceeded?
        return promotion_applied if promotion_exists_on_order?(order, promotion)

        unless promotion.eligible?(order, promotion_code:)
          set_promotion_eligibility_error_code(promotion)
          return error || ineligible_for_this_order
        end

        # If any of the actions for the promotion return `true`,
        # then result here will also be `true`.
        result = promotion.activate(order:, promotion_code:)
        if result
          order.recalculate
          set_success_code :coupon_code_applied
        else
          set_error_code :coupon_code_unknown_error
        end
      end

      def set_promotion_eligibility_error_code(promotion)
        return unless eligibility_error_code_present?(promotion)

        eligibility_error = promotion.eligibility_errors.details[:base].first

        @status_code = eligibility_error[:error_code]
        @error = eligibility_error[:error]
      end

      def promotion_usage_limit_exceeded
        set_error_code :coupon_code_max_usage
      end

      def ineligible_for_this_order
        set_error_code :coupon_code_not_eligible
      end

      def promotion_applied
        set_error_code :coupon_code_already_applied
      end

      def promotion_exists_on_order?(order, promotion)
        order.promotions.include? promotion
      end

      def eligibility_error_code_present?(promotion)
        promotion.eligibility_errors.present? &&
          promotion.eligibility_errors.details.present? &&
          promotion.eligibility_errors.details.key?(:base) &&
          promotion.eligibility_errors.details[:base].first[:error_code].present?
      end
    end
  end
end
