# frozen_string_literal: true

require "test_helper"
require "customer"
require "registration"

def execute_command(
	iq=Blather::Stanza::Iq::Command.new.tap { |i| i.from = "test@example.com" },
	blather: BLATHER,
	&blk
)
	Command::Execution.new(
		Minitest::Mock.new,
		blather,
		:to_s.to_proc,
		iq
	).execute(&blk).sync
end

class RegistrationTest < Minitest::Test
	def test_for_registered
		sgx = OpenStruct.new(
			registered?: OpenStruct.new(phone: "+15555550000")
		)
		iq = Blather::Stanza::Iq::Command.new
		iq.from = "test@example.com"
		result = execute_command(iq) do
			Registration.for(
				customer(sgx: sgx),
				Minitest::Mock.new
			)
		end
		assert_kind_of Registration::Registered, result
	end
	em :test_for_registered

	def test_for_activated
		web_manager = TelSelections.new(redis: FakeRedis.new)
		web_manager.set("test@example.net", "+15555550000")
		result = execute_command do
			sgx = OpenStruct.new(registered?: false)
			Registration.for(
				customer(
					plan_name: "test_usd",
					expires_at: Time.now + 999,
					sgx: sgx
				),
				web_manager
			)
		end
		assert_kind_of Registration::Finish, result
	end
	em :test_for_activated

	def test_for_not_activated_approved
		sgx = OpenStruct.new(registered?: false)
		web_manager = TelSelections.new(redis: FakeRedis.new)
		web_manager.set("test\\40approved.example.com@component", "+15555550000")
		iq = Blather::Stanza::Iq::Command.new
		iq.from = "test@approved.example.com"
		result = execute_command(iq) do
			Registration.for(
				customer(
					sgx: sgx,
					jid: Blather::JID.new("test\\40approved.example.com@component")
				),
				web_manager
			)
		end
		assert_kind_of Registration::Activation::Allow, result
	end
	em :test_for_not_activated_approved

	def test_for_not_activated_with_customer_id
		sgx = OpenStruct.new(registered?: false)
		web_manager = TelSelections.new(redis: FakeRedis.new)
		web_manager.set("test@example.net", "+15555550000")
		iq = Blather::Stanza::Iq::Command.new
		iq.from = "test@example.com"
		result = execute_command(iq) do
			Registration.for(
				customer(sgx: sgx),
				web_manager
			)
		end
		assert_kind_of Registration::Activation, result
	end
	em :test_for_not_activated_with_customer_id

	class ActivationTest < Minitest::Test
		Command::COMMAND_MANAGER = Minitest::Mock.new
		def setup
			@activation = Registration::Activation.new("test", "+15555550000")
		end

		def test_write
			stub_request(
				:get,
				"https://dashboard.bandwidth.com/v1.0/tns/+15555550000"
			).to_return(status: 201, body: <<~RESPONSE)
				<TelephoneNumberResponse>
					<TelephoneNumber>5555550000</TelephoneNumber>
				</TelephoneNumberResponse>
			RESPONSE
			stub_request(
				:get,
				"https://dashboard.bandwidth.com/v1.0/tns/5555550000/ratecenter"
			).to_return(status: 201, body: <<~RESPONSE)
				<TelephoneNumberResponse>
					<TelephoneNumberDetails>
						<State>KE</State>
						<RateCenter>FA</RateCenter>
					</TelephoneNumberDetails>
				</TelephoneNumberResponse>
			RESPONSE
			Command::COMMAND_MANAGER.expect(
				:write,
				EMPromise.reject(:test_result),
				[Matching.new do |iq|
					assert_equal :form, iq.form.type
					assert_equal(
						"You've selected +15555550000 (FA, KE) as your JMP number.",
						iq.form.instructions.lines.first.chomp
					)
				end]
			)
			assert_equal(
				:test_result,
				execute_command { @activation.write.catch { |e| e } }
			)
			assert_mock Command::COMMAND_MANAGER
		end
		em :test_write
	end

	class AllowTest < Minitest::Test
		Command::COMMAND_MANAGER = Minitest::Mock.new
		Registration::Activation::Allow::DB = Minitest::Mock.new

		def test_write_credit_to_nil
			cust = Minitest::Mock.new(customer("test"))
			allow = Registration::Activation::Allow.new(cust, "+15555550000", nil)

			stub_request(
				:get,
				"https://dashboard.bandwidth.com/v1.0/tns/+15555550000"
			).to_return(status: 201, body: <<~RESPONSE)
				<TelephoneNumberResponse>
					<TelephoneNumber>5555550000</TelephoneNumber>
				</TelephoneNumberResponse>
			RESPONSE
			stub_request(
				:get,
				"https://dashboard.bandwidth.com/v1.0/tns/5555550000/ratecenter"
			).to_return(status: 201, body: <<~RESPONSE)
				<TelephoneNumberResponse>
					<TelephoneNumberDetails>
						<State>KE</State>
						<RateCenter>FA</RateCenter>
					</TelephoneNumberDetails>
				</TelephoneNumberResponse>
			RESPONSE
			Command::COMMAND_MANAGER.expect(
				:write,
				EMPromise.resolve(Blather::Stanza::Iq::Command.new.tap { |iq|
					iq.form.fields = [{ var: "plan_name", value: "test_usd" }]
				}),
				[Matching.new do |iq|
					assert_equal :form, iq.form.type
					assert_equal(
						"You've selected +15555550000 (FA, KE) as your JMP number.",
						iq.form.instructions.lines.first.chomp
					)
					assert_equal 1, iq.form.fields.length
				end]
			)
			Registration::Activation::Allow::DB.expect(
				:transaction,
				EMPromise.reject(:test_result)
			) do |&blk|
				blk.call
				true
			end
			cust.expect(:with_plan, cust, ["test_usd"])
			cust.expect(:activate_plan_starting_now, nil)
			assert_equal(
				:test_result,
				execute_command { allow.write.catch { |e| e } }
			)
			assert_mock Command::COMMAND_MANAGER
		end
		em :test_write_credit_to_nil

		def test_write_credit_to_refercust
			cust = Minitest::Mock.new(customer("test"))
			allow = Registration::Activation::Allow.new(
				cust, "+15555550000", "refercust"
			)

			stub_request(
				:get,
				"https://dashboard.bandwidth.com/v1.0/tns/+15555550000"
			).to_return(status: 201, body: <<~RESPONSE)
				<TelephoneNumberResponse>
					<TelephoneNumber>5555550000</TelephoneNumber>
				</TelephoneNumberResponse>
			RESPONSE
			stub_request(
				:get,
				"https://dashboard.bandwidth.com/v1.0/tns/5555550000/ratecenter"
			).to_return(status: 201, body: <<~RESPONSE)
				<TelephoneNumberResponse>
					<TelephoneNumberDetails>
						<State>KE</State>
						<RateCenter>FA</RateCenter>
					</TelephoneNumberDetails>
				</TelephoneNumberResponse>
			RESPONSE
			Command::COMMAND_MANAGER.expect(
				:write,
				EMPromise.resolve(Blather::Stanza::Iq::Command.new.tap { |iq|
					iq.form.fields = [{ var: "plan_name", value: "test_usd" }]
				}),
				[Matching.new do |iq|
					assert_equal :form, iq.form.type
					assert_equal(
						"You've selected +15555550000 (FA, KE) as your JMP number.",
						iq.form.instructions.lines.first.chomp
					)
					assert_equal 1, iq.form.fields.length
				end]
			)
			Registration::Activation::Allow::DB.expect(
				:transaction,
				EMPromise.reject(:test_result)
			) do |&blk|
				blk.call
				true
			end
			Registration::Activation::Allow::DB.expect(
				:exec,
				nil,
				[String, ["refercust", "test"]]
			)
			cust.expect(:with_plan, cust, ["test_usd"])
			cust.expect(:activate_plan_starting_now, nil)
			assert_equal(
				:test_result,
				execute_command { allow.write.catch { |e| e } }
			)
			assert_mock Command::COMMAND_MANAGER
		end
		em :test_write_credit_to_refercust
	end

	class PaymentTest < Minitest::Test
		Customer::BRAINTREE = Minitest::Mock.new

		def test_for_bitcoin
			cust = Minitest::Mock.new(customer)
			cust.expect(
				:add_btc_address,
				EMPromise.resolve("testaddr")
			)
			iq = Blather::Stanza::Iq::Command.new
			iq.form.fields = [
				{ var: "activation_method", value: "bitcoin" },
				{ var: "plan_name", value: "test_usd" }
			]
			result = Registration::Payment.for(iq, cust, "+15555550000")
			assert_kind_of Registration::Payment::Bitcoin, result
		end

		def test_for_credit_card
			braintree_customer = Minitest::Mock.new
			Customer::BRAINTREE.expect(
				:customer,
				braintree_customer
			)
			braintree_customer.expect(
				:find,
				EMPromise.resolve(OpenStruct.new(payment_methods: [])),
				["test"]
			)
			iq = Blather::Stanza::Iq::Command.new
			iq.from = "test@example.com"
			iq.form.fields = [
				{ var: "activation_method", value: "credit_card" },
				{ var: "plan_name", value: "test_usd" }
			]
			result = Registration::Payment.for(
				iq,
				customer,
				"+15555550000"
			).sync
			assert_kind_of Registration::Payment::CreditCard, result
		end
		em :test_for_credit_card

		def test_for_code
			iq = Blather::Stanza::Iq::Command.new
			iq.form.fields = [
				{ var: "activation_method", value: "code" },
				{ var: "plan_name", value: "test_usd" }
			]
			result = Registration::Payment.for(
				iq,
				customer,
				"+15555550000"
			)
			assert_kind_of Registration::Payment::InviteCode, result
		end

		class BitcoinTest < Minitest::Test
			Registration::Payment::Bitcoin::BTC_SELL_PRICES = Minitest::Mock.new
			Customer::REDIS = Minitest::Mock.new

			def setup
				@customer = Minitest::Mock.new(
					customer(plan_name: "test_usd")
				)
				@customer.expect(
					:add_btc_address,
					EMPromise.resolve("testaddr")
				)
				@bitcoin = Registration::Payment::Bitcoin.new(
					@customer,
					"+15555550000"
				)
			end

			def test_write
				Customer::REDIS.expect(
					:smembers,
					EMPromise.resolve([]),
					["jmp_customer_btc_addresses-test"]
				)
				reply_text = <<~NOTE
					Activate your account by sending at least 1.000000 BTC to
					testaddr

					You will receive a notification when your payment is complete.
				NOTE
				blather = Minitest::Mock.new
				blather.expect(
					:<<,
					nil,
					[Matching.new do |reply|
						assert_equal :canceled, reply.status
						assert_equal :info, reply.note_type
						assert_equal reply_text, reply.note.content
						true
					end]
				)
				Registration::Payment::Bitcoin::BTC_SELL_PRICES.expect(
					:usd,
					EMPromise.resolve(BigDecimal(1))
				)
				@bitcoin.stub(:save, EMPromise.resolve(nil)) do
					execute_command(blather: blather) do
						@bitcoin.write
					end
				end
				assert_mock blather
			end
			em :test_write
		end

		class CreditCardTest < Minitest::Test
			def setup
				@credit_card = Registration::Payment::CreditCard.new(
					customer,
					"+15555550000"
				)
			end

			def test_for
				customer = Minitest::Mock.new(customer)
				customer.expect(
					:payment_methods,
					EMPromise.resolve(OpenStruct.new(default_payment_method: :test))
				)
				assert_kind_of(
					Registration::Payment::CreditCard::Activate,
					Registration::Payment::CreditCard.for(
						customer,
						"+15555550000"
					).sync
				)
			end
			em :test_for

			def test_write
				result = execute_command do
					Command::COMMAND_MANAGER.expect(
						:write,
						EMPromise.reject(:test_result),
						[Matching.new do |reply|
							assert_equal [:execute, :next], reply.allowed_actions
							assert_equal(
								"Add credit card, then return here to continue: " \
								"http://creditcard.example.com",
								reply.note.content
							)
						end]
					)

					@credit_card.write.catch { |e| e }
				end

				assert_equal :test_result, result
			end
			em :test_write
		end

		class ActivateTest < Minitest::Test
			Registration::Payment::CreditCard::Activate::Finish =
				Minitest::Mock.new
			Registration::Payment::CreditCard::Activate::Transaction =
				Minitest::Mock.new
			Command::COMMAND_MANAGER = Minitest::Mock.new

			def test_write
				transaction = PromiseMock.new
				transaction.expect(
					:insert,
					EMPromise.resolve(nil)
				)
				customer = Minitest::Mock.new(
					customer(plan_name: "test_usd")
				)
				Registration::Payment::CreditCard::Activate::Transaction.expect(
					:sale,
					transaction
				) do |acustomer, amount:, payment_method:|
					assert_operator customer, :===, acustomer
					assert_equal CONFIG[:activation_amount], amount
					assert_equal :test_default_method, payment_method
				end
				customer.expect(:bill_plan, nil)
				Registration::Payment::CreditCard::Activate::Finish.expect(
					:new,
					OpenStruct.new(write: nil),
					[customer, "+15555550000"]
				)
				execute_command do
					Registration::Payment::CreditCard::Activate.new(
						customer,
						:test_default_method,
						"+15555550000"
					).write
				end
				Registration::Payment::CreditCard::Activate::Transaction.verify
				transaction.verify
				customer.verify
				Registration::Payment::CreditCard::Activate::Finish.verify
			end
			em :test_write

			def test_write_declines
				customer = Minitest::Mock.new(
					customer(plan_name: "test_usd")
				)
				iq = Blather::Stanza::Iq::Command.new
				iq.from = "test@example.com"
				msg = Registration::Payment::CreditCard::Activate::DECLINE_MESSAGE
				Command::COMMAND_MANAGER.expect(
					:write,
					EMPromise.reject(:test_result),
					[Matching.new do |reply|
						assert_equal :error, reply.note_type
						assert_equal(
							"#{msg}: http://creditcard.example.com",
							reply.note.content
						)
					end]
				)
				result = execute_command do
					Registration::Payment::CreditCard::Activate::Transaction.expect(
						:sale,
						EMPromise.reject("declined")
					) do |acustomer, amount:, payment_method:|
						assert_operator customer, :===, acustomer
						assert_equal CONFIG[:activation_amount], amount
						assert_equal :test_default_method, payment_method
					end

					Registration::Payment::CreditCard::Activate.new(
						customer,
						:test_default_method,
						"+15555550000"
					).write.catch { |e| e }
				end
				assert_equal :test_result, result
				Registration::Payment::CreditCard::Activate::Transaction.verify
			end
			em :test_write_declines
		end

		class InviteCodeTest < Minitest::Test
			Registration::Payment::InviteCode::DB =
				Minitest::Mock.new
			Registration::Payment::InviteCode::REDIS =
				Minitest::Mock.new
			Command::COMMAND_MANAGER = Minitest::Mock.new
			Registration::Payment::InviteCode::Finish =
				Minitest::Mock.new
			def test_write
				customer = customer(plan_name: "test_usd")
				Registration::Payment::InviteCode::DB.expect(:transaction, true, [])
				Registration::Payment::InviteCode::Finish.expect(
					:new,
					OpenStruct.new(write: nil),
					[
						customer,
						"+15555550000"
					]
				)
				execute_command do
					Registration::Payment::InviteCode::REDIS.expect(
						:get,
						EMPromise.resolve(nil),
						["jmp_invite_tries-test"]
					)
					Command::COMMAND_MANAGER.expect(
						:write,
						EMPromise.resolve(
							Blather::Stanza::Iq::Command.new.tap { |iq|
								iq.form.fields = [{ var: "code", value: "abc" }]
							}
						),
						[Matching.new do |reply|
							assert_equal :form, reply.form.type
							assert_nil reply.form.instructions
						end]
					)

					Registration::Payment::InviteCode.new(
						customer,
						"+15555550000"
					).write
				end
				assert_mock Command::COMMAND_MANAGER
				assert_mock Registration::Payment::InviteCode::DB
				assert_mock Registration::Payment::InviteCode::REDIS
				assert_mock Registration::Payment::InviteCode::Finish
			end
			em :test_write

			def test_write_bad_code
				result = execute_command do
					customer = customer(plan_name: "test_usd")
					Registration::Payment::InviteCode::REDIS.expect(
						:get,
						EMPromise.resolve(0),
						["jmp_invite_tries-test"]
					)
					Registration::Payment::InviteCode::DB.expect(:transaction, []) do
						raise Registration::Payment::InviteCode::Invalid, "wut"
					end
					Registration::Payment::InviteCode::REDIS.expect(
						:incr,
						EMPromise.resolve(nil),
						["jmp_invite_tries-test"]
					)
					Registration::Payment::InviteCode::REDIS.expect(
						:expire,
						EMPromise.resolve(nil),
						["jmp_invite_tries-test", 60 * 60]
					)
					Command::COMMAND_MANAGER.expect(
						:write,
						EMPromise.resolve(
							Blather::Stanza::Iq::Command.new.tap { |iq|
								iq.form.fields = [{ var: "code", value: "abc" }]
							}
						),
						[Matching.new do |reply|
							assert_equal :form, reply.form.type
							assert_nil reply.form.instructions
						end]
					)
					Command::COMMAND_MANAGER.expect(
						:write,
						EMPromise.reject(:test_result),
						[Matching.new do |reply|
							assert_equal :form, reply.form.type
							assert_equal "wut", reply.form.instructions
						end]
					)

					Registration::Payment::InviteCode.new(
						customer,
						"+15555550000"
					).write.catch { |e| e }
				end
				assert_equal :test_result, result
				assert_mock Command::COMMAND_MANAGER
				assert_mock Registration::Payment::InviteCode::DB
				assert_mock Registration::Payment::InviteCode::REDIS
			end
			em :test_write_bad_code

			def test_write_bad_code_over_limit
				result = execute_command do
					customer = customer(plan_name: "test_usd")
					Registration::Payment::InviteCode::REDIS.expect(
						:get,
						EMPromise.resolve(11),
						["jmp_invite_tries-test"]
					)
					Command::COMMAND_MANAGER.expect(
						:write,
						EMPromise.resolve(
							Blather::Stanza::Iq::Command.new.tap { |iq|
								iq.form.fields = [{ var: "code", value: "abc" }]
							}
						),
						[Matching.new do |reply|
							assert_equal :form, reply.form.type
							assert_nil reply.form.instructions
						end]
					)
					Registration::Payment::InviteCode::REDIS.expect(
						:incr,
						EMPromise.resolve(nil),
						["jmp_invite_tries-test"]
					)
					Registration::Payment::InviteCode::REDIS.expect(
						:expire,
						EMPromise.resolve(nil),
						["jmp_invite_tries-test", 60 * 60]
					)
					Command::COMMAND_MANAGER.expect(
						:write,
						EMPromise.reject(:test_result),
						[Matching.new do |reply|
							assert_equal :form, reply.form.type
							assert_equal "Too many wrong attempts", reply.form.instructions
						end]
					)
					Registration::Payment::InviteCode.new(
						customer,
						"+15555550000"
					).write.catch { |e| e }
				end
				assert_equal :test_result, result
				assert_mock Command::COMMAND_MANAGER
				assert_mock Registration::Payment::InviteCode::REDIS
			end
			em :test_write_bad_code_over_limit
		end
	end

	class FinishTest < Minitest::Test
		Command::COMMAND_MANAGER = Minitest::Mock.new
		Registration::Finish::TEL_SELECTIONS = FakeTelSelections.new
		Registration::Finish::REDIS = Minitest::Mock.new
		Bwmsgsv2Repo::REDIS = Minitest::Mock.new

		def setup
			@sgx = Minitest::Mock.new(TrivialBackendSgxRepo.new.get("test"))
			iq = Blather::Stanza::Iq::Command.new
			iq.from = "test\\40example.com@cheogram.com"
			@finish = Registration::Finish.new(
				customer(sgx: @sgx),
				"+15555550000"
			)
		end

		def test_write
			create_order = stub_request(
				:post,
				"https://dashboard.bandwidth.com/v1.0/accounts//orders"
			).to_return(status: 201, body: <<~RESPONSE)
				<OrderResponse>
					<Order>
						<id>test_order</id>
					</Order>
				</OrderResponse>
			RESPONSE
			stub_request(
				:get,
				"https://dashboard.bandwidth.com/v1.0/accounts//orders/test_order"
			).to_return(status: 201, body: <<~RESPONSE)
				<OrderResponse>
					<OrderStatus>COMPLETE</OrderStatus>
					<CompletedNumbers>
						<TelephoneNumber>
							<FullNumber>5555550000</FullNumber>
						</TelephoneNumber>
					</CompletedNumbers>
				</OrderResponse>
			RESPONSE
			stub_request(
				:post,
				"https://dashboard.bandwidth.com/v1.0/accounts//sites//sippeers//movetns"
			)
			Registration::Finish::REDIS.expect(
				:del,
				nil,
				["pending_tel_for-test@example.net"]
			)
			Bwmsgsv2Repo::REDIS.expect(
				:set,
				nil,
				[
					"catapult_fwd-+15555550000",
					"xmpp:test@example.net"
				]
			)
			Bwmsgsv2Repo::REDIS.expect(
				:set,
				nil,
				["catapult_fwd_timeout-customer_test@component", 25]
			)
			blather = Minitest::Mock.new
			blather.expect(
				:<<,
				nil,
				[Matching.new do |reply|
					assert_equal :completed, reply.status
					assert_equal :info, reply.note_type
					assert_equal(
						"Your JMP account has been activated as +15555550000",
						reply.note.content
					)
				end]
			)
			execute_command(blather: blather) do
				@sgx.expect(
					:register!,
					EMPromise.resolve(@sgx.with(registered?: IBR.new.tap do |ibr|
						ibr.phone = "+15555550000"
					end)),
					["+15555550000"]
				)

				@finish.write
			end
			assert_requested create_order
			assert_mock @sgx
			assert_mock Registration::Finish::REDIS
			assert_mock Bwmsgsv2Repo::REDIS
			assert_mock blather
		end
		em :test_write

		def test_write_tn_fail
			create_order = stub_request(
				:post,
				"https://dashboard.bandwidth.com/v1.0/accounts//orders"
			).to_return(status: 201, body: <<~RESPONSE)
				<OrderResponse>
					<Order>
						<id>test_order</id>
					</Order>
				</OrderResponse>
			RESPONSE
			stub_request(
				:get,
				"https://dashboard.bandwidth.com/v1.0/accounts//orders/test_order"
			).to_return(status: 201, body: <<~RESPONSE)
				<OrderResponse>
					<OrderStatus>FAILED</OrderStatus>
				</OrderResponse>
			RESPONSE

			result = execute_command do
				Command::COMMAND_MANAGER.expect(
					:write,
					EMPromise.reject(:test_result),
					[Matching.new do |iq|
						assert_equal :form, iq.form.type
						assert_equal(
							"The JMP number +15555550000 is no longer available.",
							iq.form.instructions
						)
					end]
				)

				@finish.write.catch { |e| e }
			end

			assert_equal :test_result, result
			assert_mock Command::COMMAND_MANAGER
			assert_instance_of(
				TelSelections::ChooseTel,
				Registration::Finish::TEL_SELECTIONS["test@example.com"]
			)
			assert_requested create_order
		end
		em :test_write_tn_fail
	end
end
