# frozen_string_literal: true

require "bigdecimal/util"
require "delegate"

require_relative "transaction"
require_relative "trust_level_repo"

class CreditCardSale
	def self.create(*args, transaction_class: Transaction, **kwargs)
		new(*args, **kwargs).sale.then do |response|
			tx = BraintreeTransaction.build(
				response,
				transaction_class: transaction_class
			)
			tx.insert.then { tx }
		end
	end

	class BraintreeTransaction < SimpleDelegator
		def self.build(braintree_transaction, transaction_class: Transaction)
			new(braintree_transaction).to_transaction(transaction_class)
		end

		def to_transaction(transaction_class)
			transaction_class.new(
				customer_id: customer_details.id,
				transaction_id: id,
				created_at: created_at,
				settled_after: created_at + (90 * 24 * 60 * 60),
				amount: amount,
				note: "Credit card payment"
			)
		end
	end

	def initialize(
		customer, amount:, payment_method: nil,
		trust_repo: TrustLevelRepo.new
	)
		@customer = customer
		@amount = amount
		@payment_method = payment_method
		@trust_repo = trust_repo
	end

	def sale
		EMPromise.all([validate!, resolve_payment_method]).then { |_, selected|
			BRAINTREE.transaction.sale(
				amount: @amount,
				merchant_account_id: @customer.merchant_account,
				options: { submit_for_settlement: true },
				payment_method_token: selected.token
			)
		}.then { |response| decline_guard(response) }
	end

protected

	def validate!
		EMPromise.all([
			REDIS.exists("jmp_customer_credit_card_lock-#{@customer.customer_id}"),
			@trust_repo.find(@customer), @customer.declines
		]).then do |(lock, tl, declines)|
			unless tl.credit_card_transaction?(@amount.to_d, declines)
				raise "Declined"
			end
			raise "Too many payments recently" if lock == 1
		end
	end

	def resolve_payment_method
		EMPromise.all([
			@payment_method ||
				@customer.payment_methods.then(&:default_payment_method)
		]).then do |(selected_method)|
			raise "No valid payment method on file" unless selected_method

			selected_method
		end
	end

	def decline_guard(response)
		if response.success?
			REDIS.setex(
				"jmp_customer_credit_card_lock-#{@customer.customer_id}",
				60 * 60 * 24,
				"1"
			)
			return response.transaction
		end

		@customer.mark_decline
		raise BraintreeFailure, response
	end
end

class BraintreeFailure < StandardError
	attr_reader :response

	def initialize(response)
		super response.message
		@response = response
	end
end
