# frozen_string_literal: true

require_relative "expiring_lock"
require_relative "transaction"
require_relative "credit_card_sale"

class LowBalance
	def self.for(customer, transaction_amount=0)
		locked_if_no_services(customer).then do |locked|
			locked || ExpiringLock.new(
				"jmp_customer_low_balance-#{customer.billing_customer_id}",
				expiry: 60 * 60 * 24 * 7
			).with(-> { Locked.new }) do
				customer.billing_customer.then do |billing_customer|
					for_no_lock(billing_customer, transaction_amount)
				end
			end
		end
	end

	def self.locked_if_no_services(customer)
		return if customer.registered?

		DB.query_defer(
			"SELECT COUNT(*) AS c FROM sims WHERE customer_id=$1",
			[customer.customer_id]
		).then do |result|
			next if result.first["c"].to_i.positive?

			Locked.new
		end
	end

	def self.for_no_lock(customer, transaction_amount, auto: true)
		if auto && customer.auto_top_up_amount.positive?
			AutoTopUp.for(customer, transaction_amount)
		else
			customer.btc_addresses.then do |btc_addresses|
				new(customer, btc_addresses, transaction_amount)
			end
		end
	end

	def initialize(customer, btc_addresses, transaction_amount=0)
		@customer = customer
		@btc_addresses = btc_addresses
		@transaction_amount = transaction_amount
	end

	def can_top_up?
		false
	end

	def notify!
		m = Blather::Stanza::Message.new
		m.from = CONFIG[:notify_from]
		m.body =
			"Your balance of $#{'%.4f' % @customer.balance} is low." \
			"#{pending_cost_for_notification}" \
			"#{btc_addresses_for_notification}"
		@customer.stanza_to(m)
		EMPromise.resolve(0)
	end

	def pending_cost_for_notification
		return unless @transaction_amount&.positive?
		return unless @transaction_amount > @customer.balance

		"\nYou need an additional " \
		"$#{'%.2f' % (@transaction_amount - @customer.balance)} "\
		"to complete this transaction."
	end

	def btc_addresses_for_notification
		return if @btc_addresses.empty?

		"\nYou can buy credit by sending any amount of Bitcoin to one of " \
		"these addresses:\n#{@btc_addresses.join("\n")}"
	end

	class AutoTopUp
		def self.for(customer, target=0)
			customer.payment_methods.then(&:default_payment_method).then do |method|
				blocked?(method).then do |block|
					next AutoTopUp.new(customer, method, target) if block.zero?

					log.info("#{customer.customer_id} auto top up blocked")
					LowBalance.for_no_lock(customer, target, auto: false)
				end
			end
		end

		def self.blocked?(method)
			return EMPromise.resolve(1) if method.nil?

			REDIS.exists(
				"jmp_auto_top_up_block-#{method&.unique_number_identifier}"
			)
		end

		def initialize(customer, method=nil, target=0, margin: 10)
			@customer = customer
			@method = method
			@target = target
			@margin = margin
			@message = Blather::Stanza::Message.new
			@message.from = CONFIG[:notify_from]
		end

		def top_up_amount
			[
				((@target + @margin) - @customer.balance).round(2),
				@customer.auto_top_up_amount
			].max
		end

		def can_top_up?
			true
		end

		def sale
			CreditCardSale.create(@customer, amount: top_up_amount)
		end

		def failed(e)
			@method && REDIS.setex(
				"jmp_auto_top_up_block-#{@method.unique_number_identifier}",
				60 * 60 * 24 * 30,
				Time.now
			)
			@message.body =
				"Automatic top-up transaction for " \
				"$#{'%.2f' % top_up_amount} failed: #{e.message}"
			0
		end

		def notify!
			sale.then { |tx|
				@message.body =
					"Automatic top-up has charged your default " \
					"payment method and added #{tx} to your balance."
				tx.total
			}.catch(&method(:failed)).then { |amount|
				@customer.stanza_to(@message)
				amount
			}
		end
	end

	class Locked
		def notify!
			EMPromise.resolve(0)
		end
	end
end
