# frozen_string_literal: true

require "test_helper"
require "credit_card_sale"
require "customer"
require "transaction"
require "ostruct"

CreditCardSale::BRAINTREE = Minitest::Mock.new
CreditCardSale::REDIS = Minitest::Mock.new
TrustLevelRepo::REDIS = Minitest::Mock.new
TrustLevelRepo::DB = Minitest::Mock.new
CustomerFinancials::REDIS = Minitest::Mock.new

class CreditCardSaleTest < Minitest::Test
	FAKE_BRAINTREE_TRANSACTION =
		OpenStruct.new(
			customer_details: OpenStruct.new(id: "customer"),
			id: "transaction",
			created_at: Time.at(0),
			amount: 12
		)

	def test_sale_fails
		CreditCardSale::REDIS.expect(
			:exists,
			EMPromise.resolve(0),
			["jmp_customer_credit_card_lock-test"]
		)
		TrustLevelRepo::REDIS.expect(
			:get,
			EMPromise.resolve("Customer"),
			["jmp_customer_trust_level-test"]
		)
		TrustLevelRepo::DB.expect(
			:query_one,
			EMPromise.resolve({}),
			[String, "test"], default: {}
		)
		CustomerFinancials::REDIS.expect(
			:get,
			EMPromise.resolve("1"),
			["jmp_pay_decline-test"]
		)
		CustomerFinancials::REDIS.expect(
			:incr,
			EMPromise.resolve(nil),
			["jmp_pay_decline-test"]
		)
		CustomerFinancials::REDIS.expect(
			:expire,
			EMPromise.resolve(nil),
			["jmp_pay_decline-test", 60 * 60 * 24]
		)
		braintree_transaction = Minitest::Mock.new
		CreditCardSale::BRAINTREE.expect(:transaction, braintree_transaction)
		braintree_transaction.expect(
			:sale,
			EMPromise.resolve(
				OpenStruct.new(success?: false, message: "declined")
			),
			amount: 99,
			merchant_account_id: "merchant_usd",
			options: { submit_for_settlement: true },
			payment_method_token: "token"
		)
		assert_raises(BraintreeFailure) do
			CreditCardSale.new(
				customer(plan_name: "test_usd"),
				amount: 99,
				payment_method: OpenStruct.new(token: "token")
			).sale.sync
		end
		assert_mock CustomerFinancials::REDIS
		assert_mock CreditCardSale::REDIS
		assert_mock TrustLevelRepo::REDIS
		assert_mock TrustLevelRepo::DB
	end
	em :test_sale_fails

	def test_sale_locked
		CreditCardSale::REDIS.expect(
			:exists,
			EMPromise.resolve(1),
			["jmp_customer_credit_card_lock-test"]
		)
		TrustLevelRepo::REDIS.expect(
			:get,
			EMPromise.resolve("Customer"),
			["jmp_customer_trust_level-test"]
		)
		TrustLevelRepo::DB.expect(
			:query_one,
			EMPromise.resolve({}),
			[String, "test"], default: {}
		)
		CustomerFinancials::REDIS.expect(
			:get,
			EMPromise.resolve("0"),
			["jmp_pay_decline-test"]
		)
		assert_raises("locked") do
			CreditCardSale.new(
				customer(plan_name: "test_usd"),
				amount: 123,
				payment_method: OpenStruct.new(token: "token")
			).sale.sync
		end
		assert_mock CustomerFinancials::REDIS
		assert_mock CreditCardSale::REDIS
		assert_mock TrustLevelRepo::REDIS
		assert_mock TrustLevelRepo::DB
	end
	em :test_sale_locked

	def test_sale_amount_too_high
		CreditCardSale::REDIS.expect(
			:exists,
			EMPromise.resolve(0),
			["jmp_customer_credit_card_lock-test"]
		)
		TrustLevelRepo::REDIS.expect(
			:get,
			EMPromise.resolve("Customer"),
			["jmp_customer_trust_level-test"]
		)
		TrustLevelRepo::DB.expect(
			:query_one,
			EMPromise.resolve({}),
			[String, "test"], default: {}
		)
		CustomerFinancials::REDIS.expect(
			:get,
			EMPromise.resolve("0"),
			["jmp_pay_decline-test"]
		)

		assert_raises(AmountTooHighError) do
			CreditCardSale.new(
				customer(plan_name: "test_usd"),
				amount: 131,
				payment_method: OpenStruct.new(token: "token")
			).sale.sync
		end

		assert_mock CustomerFinancials::REDIS
		assert_mock CreditCardSale::REDIS
		assert_mock TrustLevelRepo::REDIS
		assert_mock TrustLevelRepo::DB
	end
	em :test_sale_amount_too_high

	def test_sale_too_many_declines
		CreditCardSale::REDIS.expect(
			:exists,
			EMPromise.resolve(0),
			["jmp_customer_credit_card_lock-test"]
		)
		TrustLevelRepo::REDIS.expect(
			:get,
			EMPromise.resolve("Customer"),
			["jmp_customer_trust_level-test"]
		)
		TrustLevelRepo::DB.expect(
			:query_one,
			EMPromise.resolve({}),
			[String, "test"], default: {}
		)
		CustomerFinancials::REDIS.expect(
			:get,
			EMPromise.resolve("3"),
			["jmp_pay_decline-test"]
		)

		assert_raises(DeclinedError) do
			CreditCardSale.new(
				customer(plan_name: "test_usd"),
				amount: 50,
				payment_method: OpenStruct.new(token: "token")
			).sale.sync
		end

		assert_mock CustomerFinancials::REDIS
		assert_mock CreditCardSale::REDIS
		assert_mock TrustLevelRepo::REDIS
		assert_mock TrustLevelRepo::DB
	end
	em :test_sale_too_many_declines

	def test_sale
		req = stub_request(
			:post,
			"https://api.churnbuster.io/v1/successful_payments"
		).with(
			body: {
				customer: {
					source: "braintree",
					source_id: "test",
					email: "test@smtp.cheogram.com",
					properties: {}
				},
				payment: {
					source: "braintree",
					source_id: "transaction",
					amount_in_cents: 9900,
					currency: "USD"
				}
			}.to_json
		).to_return(status: 200, body: "", headers: {})

		CreditCardSale::REDIS.expect(
			:exists,
			EMPromise.resolve(0),
			["jmp_customer_credit_card_lock-test"]
		)
		TrustLevelRepo::REDIS.expect(
			:get,
			EMPromise.resolve("Customer"),
			["jmp_customer_trust_level-test"]
		)
		TrustLevelRepo::DB.expect(
			:query_one,
			EMPromise.resolve({}),
			[String, "test"], default: {}
		)
		CustomerFinancials::REDIS.expect(
			:get,
			EMPromise.resolve("1"),
			["jmp_pay_decline-test"]
		)
		braintree_transaction = Minitest::Mock.new
		CreditCardSale::BRAINTREE.expect(:transaction, braintree_transaction)
		braintree_transaction.expect(
			:sale,
			EMPromise.resolve(
				OpenStruct.new(
					success?: true,
					transaction: FAKE_BRAINTREE_TRANSACTION
				)
			),
			amount: 99,
			payment_method_token: "token",
			merchant_account_id: "merchant_usd",
			options: { submit_for_settlement: true }
		)
		CreditCardSale::REDIS.expect(
			:setex,
			EMPromise.resolve(1),
			["jmp_customer_credit_card_lock-test", 86400, "1"]
		)
		result = CreditCardSale.new(
			customer(plan_name: "test_usd"),
			amount: 99,
			payment_method: OpenStruct.new(token: "token")
		).sale.sync
		assert_equal FAKE_BRAINTREE_TRANSACTION, result
		assert_mock CustomerFinancials::REDIS
		assert_mock CreditCardSale::REDIS
		assert_mock TrustLevelRepo::REDIS
		assert_mock TrustLevelRepo::DB
		assert_requested req
	end
	em :test_sale

	def test_builder
		expected = Transaction.new(
			customer_id: "customer",
			transaction_id: "transaction",
			created_at: Time.at(0),
			settled_after: Time.at(7776000),
			amount: 12,
			note: "Credit card payment"
		)

		assert_equal(
			expected,
			CreditCardSale::BraintreeTransaction.build(FAKE_BRAINTREE_TRANSACTION)
		)
	end

	def test_create
		req = stub_request(
			:post,
			"https://api.churnbuster.io/v1/successful_payments"
		).with(
			body: {
				customer: {
					source: "braintree",
					source_id: "test",
					email: "test@smtp.cheogram.com",
					properties: {}
				},
				payment: {
					source: "braintree",
					source_id: "transaction",
					amount_in_cents: 9900,
					currency: "USD"
				}
			}.to_json
		).to_return(status: 200, body: "", headers: {})

		CreditCardSale::REDIS.expect(
			:exists,
			EMPromise.resolve(0),
			["jmp_customer_credit_card_lock-test"]
		)
		TrustLevelRepo::REDIS.expect(
			:get,
			EMPromise.resolve("Customer"),
			["jmp_customer_trust_level-test"]
		)
		TrustLevelRepo::DB.expect(
			:query_one,
			EMPromise.resolve({}),
			[String, "test"], default: {}
		)
		CustomerFinancials::REDIS.expect(
			:get,
			EMPromise.resolve("1"),
			["jmp_pay_decline-test"]
		)
		braintree_transaction = Minitest::Mock.new
		CreditCardSale::BRAINTREE.expect(:transaction, braintree_transaction)
		response = EMPromise.resolve(
			OpenStruct.new(
				success?: true,
				transaction: FAKE_BRAINTREE_TRANSACTION
			)
		)
		braintree_transaction.expect(
			:sale,
			response,
			amount: 99,
			payment_method_token: "token",
			merchant_account_id: "merchant_usd",
			options: { submit_for_settlement: true }
		)
		CreditCardSale::REDIS.expect(
			:setex,
			EMPromise.resolve(1),
			["jmp_customer_credit_card_lock-test", 86400, "1"]
		)

		transaction = PromiseMock.new
		transaction.expect(:insert, EMPromise.resolve(nil))

		transaction_class = Minitest::Mock.new
		transaction_class.expect(
			:new,
			transaction,
			customer_id: "customer",
			transaction_id: "transaction",
			created_at: Time.at(0),
			settled_after: Time.at(7776000),
			amount: 12,
			note: "Credit card payment"
		)

		result = CreditCardSale.create(
			customer(plan_name: "test_usd"),
			amount: 99,
			payment_method: OpenStruct.new(token: "token"),
			transaction_class: transaction_class
		).sync

		assert_equal transaction.object_id, result.object_id
		assert_mock transaction_class
		assert_mock transaction
		assert_mock CustomerFinancials::REDIS
		assert_mock CreditCardSale::REDIS
		assert_mock TrustLevelRepo::REDIS
		assert_mock TrustLevelRepo::DB
		assert_requested req
	end
	em :test_create
end
