# frozen_string_literal: true

require "rack/test"
require "test_helper"
require "bwmsgsv2_repo"
require "customer_repo"
require_relative "../web"

ExpiringLock::REDIS = Minitest::Mock.new
Customer::BLATHER = Minitest::Mock.new
CustomerFwd::BANDWIDTH_VOICE = Minitest::Mock.new
Web::BANDWIDTH_VOICE = Minitest::Mock.new
LowBalance::AutoTopUp::CreditCardSale = Minitest::Mock.new

ReachableRedis = Minitest::Mock.new

class WebTest < Minitest::Test
	include Rack::Test::Methods

	def app
		Web.opts[:customer_repo] = CustomerRepo.new(
			redis: FakeRedis.new(
				"jmp_customer_jid-customerid" => "customer@example.com",
				"catapult_jid-+15551234567" => "customer_customerid@component",
				"jmp_customer_jid-customerid_low" => "customer@example.com",
				"catapult_jid-+15551234560" => "customer_customerid_low@component",
				"jmp_customer_jid-customerid_topup" => "customer@example.com",
				"jmp_customer_auto_top_up_amount-customerid_topup" => "15",
				"jmp_customer_monthly_overage_limit-customerid_topup" => "99999",
				"catapult_jid-+15551234562" => "customer_customerid_topup@component",
				"jmp_customer_jid-customerid_limit" => "customer@example.com",
				"catapult_jid-+15551234561" => "customer_customerid_limit@component",
				"jmp_customer_jid-customerid_reach" => "customerid_reach@example.com",
				"catapult_jid-+15551234563" => "customer_customerid_reach@component",
				"jmp_customer_jid-customerid2" => "customer2@example.com",
				"catapult_jid-+15551230000" => "customer_customerid2@component"
			),
			db: FakeDB.new(
				["customerid"] => [{
					"balance" => BigDecimal(10),
					"plan_name" => "test_usd",
					"expires_at" => Time.now + 100
				}],
				["customerid2"] => [{
					"balance" => BigDecimal(10),
					"plan_name" => "test_usd",
					"expires_at" => Time.now + 100
				}],
				["customerid_low"] => [{
					"balance" => BigDecimal("0.01"),
					"plan_name" => "test_usd",
					"expires_at" => Time.now + 100
				}],
				["customerid_topup"] => [{
					"balance" => BigDecimal("0.01"),
					"plan_name" => "test_usd",
					"expires_at" => Time.now + 100
				}],
				["customerid_reach"] => [{
					"balance" => BigDecimal(10),
					"plan_name" => "test_usd",
					"expires_at" => Time.now + 100
				}],
				["customerid_limit"] => [{
					"balance" => BigDecimal(10),
					"plan_name" => "test_usd",
					"expires_at" => Time.now + 100
				}]
			),
			sgx_repo: Bwmsgsv2Repo.new(
				redis: FakeRedis.new(
					"catapult_fwd-+15551234567" => "xmpp:customer@example.com",
					"catapult_fwd_timeout-customer_customerid@component" => "30",
					"catapult_fwd-+15551234560" => "xmpp:customer@example.com",
					"catapult_fwd_timeout-customer_customerid_low@component" => "30",
					"catapult_fwd-+15551234561" => "xmpp:customer@example.com",
					"catapult_fwd_timeout-customer_customerid_limit@component" => "30",
					"catapult_fwd-+15551234563" => "xmpp:customer@example.com",
					"catapult_fwd_timeout-customer_customerid_reach@component" => "30",
					"catapult_fwd-+15551230000" => "xmpp:customer2@example.com",
					"catapult_fwd_timeout-customer_customerid2@component" => "30"
				),
				ibr_repo: FakeIBRRepo.new(
					"sgx" => {
						"customer_customerid@component" =>
							Blather::Stanza::Iq::IBR.new.tap do |ibr|
								ibr.phone = "+15551234567"
							end,
						"customer_customerid_low@component" =>
							Blather::Stanza::Iq::IBR.new.tap do |ibr|
								ibr.phone = "+15551234567"
							end,
						"customer_customerid_topup@component" =>
							Blather::Stanza::Iq::IBR.new.tap do |ibr|
								ibr.phone = "+15551234567"
							end,
						"customer_customerid_reach@component" =>
							Blather::Stanza::Iq::IBR.new.tap do |ibr|
								ibr.phone = "+15551234563"
							end,
						"customer_customerid_limit@component" =>
							Blather::Stanza::Iq::IBR.new.tap do |ibr|
								ibr.phone = "+15551234567"
							end
					}
				)
			)
		)
		Web.opts[:call_attempt_repo] = CallAttemptRepo.new(
			redis: FakeRedis.new,
			db: FakeDB.new(
				["test_usd", "+15557654321", :outbound] => [{ "rate" => 0.01 }],
				["test_usd", "+1911", :outbound] => [{ "rate" => 0.01 }],
				["test_usd", "+15557654321", :inbound] => [{ "rate" => 0.01 }],
				["test_usd", "+14445556666", :inbound] => [{ "rate" => 0.01 }],
				["test_usd", "+18001234567", :outbound] => [{ "rate" => 0.00 }],
				["customerid_limit"] => FakeDB::MultiResult.new(
					[{ "a" => 1000 }],
					[{ "settled_amount" => 15 }]
				),
				["customerid_low"] => FakeDB::MultiResult.new(
					[{ "a" => 1000 }],
					[{ "settled_amount" => 15 }]
				),
				["customerid_topup"] => FakeDB::MultiResult.new(
					[{ "a" => 1000 }],
					[{ "settled_amount" => 15 }]
				)
			)
		)
		Web.opts[:cdr_repo] = CDRRepo.new(db: FakeDB.new)
		Web.opts[:common_logger] = FakeLog.new
		Web.opts[:reachability_repo] = ReachabilityRepo::Voice.new(
			redis: ReachableRedis,
			senders: ["+14445556666"]
		)
		Web.instance_variable_set(:@outbound_transfers, { "bcall" => "oocall" })
		Web.app
	end

	def test_outbound_forwards
		post(
			"/outbound/calls",
			{
				from: "ccustomerid",
				to: "+15557654321",
				callId: "acall"
			}.to_json,
			{ "CONTENT_TYPE" => "application/json" }
		)

		assert last_response.ok?
		assert_equal(
			"<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
			"<Transfer transferCallerId=\"+15551234567\">" \
			"<PhoneNumber>+15557654321</PhoneNumber></Transfer></Response>",
			last_response.body
		)
	end
	em :test_outbound_forwards

	def test_outbound_low_balance
		ExpiringLock::REDIS.expect(
			:set,
			EMPromise.resolve(nil),
			["jmp_customer_low_balance-customerid_low", Time, "EX", 604800, "NX"]
		)

		post(
			"/outbound/calls",
			{
				from: "ccustomerid_low",
				to: "+15557654321",
				callId: "acall"
			}.to_json,
			{ "CONTENT_TYPE" => "application/json" }
		)

		assert last_response.ok?
		assert_equal(
			"<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
			"<SpeakSentence>Your balance of $0.01 is not enough to " \
			"complete this call.</SpeakSentence></Response>",
			last_response.body
		)
		assert_mock ExpiringLock::REDIS
	end
	em :test_outbound_low_balance

	def test_outbound_low_balance_top_up
		LowBalance::AutoTopUp::CreditCardSale.expect(
			:create,
			EMPromise.resolve(
				OpenStruct.new(total: 15)
			),
			[Customer], amount: 15
		)

		ExpiringLock::REDIS.expect(
			:set,
			EMPromise.resolve("OK"),
			["jmp_customer_low_balance-customerid_topup", Time, "EX", 604800, "NX"]
		)

		CustomerFinancials::REDIS.expect(
			:smembers,
			EMPromise.resolve([]),
			["block_credit_cards"]
		)
		LowBalance::AutoTopUp::REDIS.expect(
			:exists,
			0,
			["jmp_auto_top_up_block-abcd"]
		)
		braintree_customer = Minitest::Mock.new
		CustomerFinancials::BRAINTREE.expect(:customer, braintree_customer)
		payment_methods = OpenStruct.new(payment_methods: [
			OpenStruct.new(default?: true, unique_number_identifier: "abcd")
		])
		braintree_customer.expect(
			:find,
			EMPromise.resolve(payment_methods),
			["customerid_topup"]
		)

		Customer::BLATHER.expect(
			:<<,
			nil,
			[Blather::Stanza]
		)

		post(
			"/outbound/calls",
			{
				from: "ccustomerid_topup",
				to: "+15557654321",
				callId: "acall"
			}.to_json,
			{ "CONTENT_TYPE" => "application/json" }
		)

		assert last_response.ok?
		assert_equal(
			"<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
			"<Transfer transferCallerId=\"+15551234567\">" \
			"<PhoneNumber>+15557654321</PhoneNumber></Transfer></Response>",
			last_response.body
		)
		assert_mock ExpiringLock::REDIS
		assert_mock Customer::BLATHER
		assert_mock LowBalance::AutoTopUp::CreditCardSale
	end
	em :test_outbound_low_balance_top_up

	def test_outbound_unsupported
		post(
			"/outbound/calls",
			{
				from: "ccustomerid",
				to: "+95557654321",
				callId: "acall"
			}.to_json,
			{ "CONTENT_TYPE" => "application/json" }
		)

		assert last_response.ok?
		assert_equal(
			"<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
			"<SpeakSentence>The number you have dialled is not " \
			"supported on your account.</SpeakSentence></Response>",
			last_response.body
		)
	end
	em :test_outbound_unsupported

	def test_outbound_unsupported_short_numbers_911
		post(
			"/outbound/calls",
			{
				from: "ccustomerid",
				to: "+1911",
				callId: "acall"
			}.to_json,
			{ "CONTENT_TYPE" => "application/json" }
		)

		assert last_response.ok?
		assert_equal(
			"<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
			"<SpeakSentence>The number you have dialled is not " \
			"supported on your account.</SpeakSentence></Response>",
			last_response.body
		)
	end
	em :test_outbound_unsupported_short_numbers_911

	def test_outbound_atlimit
		post(
			"/outbound/calls",
			{
				from: "ccustomerid_limit",
				to: "+15557654321",
				callId: "acall"
			}.to_json,
			{ "CONTENT_TYPE" => "application/json" }
		)

		assert last_response.ok?
		assert_equal(
			"<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
			"<Gather gatherUrl=\"\/outbound/calls\" maxDigits=\"1\" " \
			"repeatCount=\"3\"><SpeakSentence>This call will take you over " \
			"your configured monthly overage limit.</SpeakSentence><SpeakSentence>" \
			"Change your limit in your account settings or press 1 to accept the " \
			"charges. You can hang up to cancel.</SpeakSentence></Gather></Response>",
			last_response.body
		)
	end
	em :test_outbound_atlimit

	def test_outbound_no_customer
		post(
			"/outbound/calls",
			{
				from: "no_such_customer",
				to: "+15557654321",
				callId: "acall"
			}.to_json,
			{ "CONTENT_TYPE" => "application/json" }
		)

		assert last_response.ok?
		assert_equal(
			"<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
			"<SpeakSentence>Your credentials are invalid, please contact support." \
			"</SpeakSentence></Response>",
			last_response.body
		)
	end
	em :test_outbound_no_customer

	def test_outbound_atlimit_digits
		post(
			"/outbound/calls",
			{
				from: "ccustomerid_limit",
				to: "+15557654321",
				callId: "acall",
				digits: "1"
			}.to_json,
			{ "CONTENT_TYPE" => "application/json" }
		)

		assert last_response.ok?
		assert_equal(
			"<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
			"<Transfer transferCallerId=\"+15551234567\">" \
			"<PhoneNumber>+15557654321</PhoneNumber></Transfer></Response>",
			last_response.body
		)
	end
	em :test_outbound_atlimit_digits

	def test_outbound_toll_free
		post(
			"/outbound/calls",
			{
				from: "ccustomerid",
				to: "+18001234567",
				callId: "acall"
			}.to_json,
			{ "CONTENT_TYPE" => "application/json" }
		)

		assert last_response.ok?
		assert_equal(
			"<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
			"<Transfer transferCallerId=\"+15551234567\">" \
			"<PhoneNumber>+18001234567</PhoneNumber></Transfer></Response>",
			last_response.body
		)
	end
	em :test_outbound_toll_free

	def test_outbound_disconnect
		post(
			"/outbound/calls/status",
			{
				eventType: "disconnect",
				from: "ccustomerid",
				to: "+15557654321",
				callId: "acall",
				startTime: Time.now.to_s,
				endTime: Time.now.to_s,
				cause: "hangup"
			}.to_json,
			{ "CONTENT_TYPE" => "application/json" }
		)

		assert last_response.ok?
		assert_equal("OK", last_response.body)
	end
	em :test_outbound_disconnect

	def test_inbound
		CustomerFwd::BANDWIDTH_VOICE.expect(
			:create_call,
			OpenStruct.new(data: OpenStruct.new(call_id: "ocall")),
			["test_bw_account"],
			body: Matching.new do |arg|
				assert_equal(
					"http://example.org/inbound/calls/acall?customer_id=customerid",
					arg.answer_url
				)
			end
		)

		post(
			"/inbound/calls",
			{
				from: "+15557654321",
				to: "+15551234567",
				callId: "acall"
			}.to_json,
			{ "CONTENT_TYPE" => "application/json" }
		)

		assert last_response.ok?
		assert_equal(
			"<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
			"<Ring answerCall=\"false\" duration=\"300\" />" \
			"</Response>",
			last_response.body
		)
		assert_mock CustomerFwd::BANDWIDTH_VOICE
	end
	em :test_inbound

	def test_inbound_from_reachability
		CustomerFwd::BANDWIDTH_VOICE.expect(
			:create_call,
			OpenStruct.new(data: OpenStruct.new(call_id: "ocall")),
			["test_bw_account"],
			body: Matching.new do |arg|
				assert_equal(
					"http://example.org/inbound/calls/acall?customer_id=customerid",
					arg.answer_url
				)
			end
		)

		ReachableRedis.expect(
			:exists,
			EMPromise.resolve(0),
			["jmp_customer_reachability_voice-customerid"]
		)

		post(
			"/inbound/calls",
			{
				from: "+14445556666",
				to: "+15551234567",
				callId: "acall"
			}.to_json,
			{ "CONTENT_TYPE" => "application/json" }
		)

		assert last_response.ok?
		assert_equal(
			"<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
			"<Ring answerCall=\"false\" duration=\"300\" />" \
			"</Response>",
			last_response.body
		)
		assert_mock CustomerFwd::BANDWIDTH_VOICE
		assert_mock ReachableRedis
	end
	em :test_inbound_from_reachability

	def test_inbound_no_bwmsgsv2
		CustomerFwd::BANDWIDTH_VOICE.expect(
			:create_call,
			OpenStruct.new(data: OpenStruct.new(call_id: "ocall")),
			["test_bw_account"],
			body: Matching.new do |arg|
				assert_equal(
					"http://example.org/inbound/calls/acall?customer_id=customerid2",
					arg.answer_url
				)
			end
		)

		post(
			"/inbound/calls",
			{
				from: "+15557654321",
				to: "+15551230000",
				callId: "acall"
			}.to_json,
			{ "CONTENT_TYPE" => "application/json" }
		)

		assert last_response.ok?
		assert_equal(
			"<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
			"<Ring answerCall=\"false\" duration=\"300\" />" \
			"</Response>",
			last_response.body
		)
		assert_mock CustomerFwd::BANDWIDTH_VOICE
	end
	em :test_inbound_no_bwmsgsv2

	def test_inbound_low
		ExpiringLock::REDIS.expect(
			:set,
			EMPromise.resolve(nil),
			["jmp_customer_low_balance-customerid_low", Time, "EX", 604800, "NX"]
		)

		post(
			"/inbound/calls",
			{
				from: "+15557654321",
				to: "+15551234560",
				callId: "acall"
			}.to_json,
			{ "CONTENT_TYPE" => "application/json" }
		)

		assert last_response.ok?
		assert_equal(
			"<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
			"<Redirect redirectUrl=\"/inbound/calls/acall/voicemail\" />" \
			"</Response>",
			last_response.body
		)
		assert_mock CustomerFwd::BANDWIDTH_VOICE
		assert_mock ExpiringLock::REDIS
	end
	em :test_inbound_low

	def test_inbound_leg2
		post(
			"/inbound/calls/acall?customer_id=customerid",
			{
				from: "+15557654321",
				to: "sip:boop@example.com",
				callId: "ocall"
			}.to_json,
			{ "CONTENT_TYPE" => "application/json" }
		)

		assert last_response.ok?
		assert_equal(
			"<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
			"<Tag>connected</Tag><Bridge>acall</Bridge>" \
			"</Response>",
			last_response.body
		)
	end
	em :test_inbound_leg2

	def test_inbound_limit_leg2
		path = "/inbound/calls/acall?customer_id=customerid_limit"

		post(
			path,
			{
				from: "+15557654321",
				to: "sip:boop@example.com",
				callId: "ocall"
			}.to_json,
			{ "CONTENT_TYPE" => "application/json" }
		)

		assert last_response.ok?
		assert_equal(
			"<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
			"<Gather gatherUrl=\"#{path}\" maxDigits=\"1\" " \
			"repeatCount=\"3\"><SpeakSentence>This call will take you over " \
			"your configured monthly overage limit.</SpeakSentence><SpeakSentence>" \
			"Change your limit in your account settings or press 1 to accept the " \
			"charges. You can hang up to send the caller to voicemail." \
			"</SpeakSentence></Gather></Response>",
			last_response.body
		)
	end
	em :test_inbound_limit_leg2

	def test_inbound_limit_digits_leg2
		post(
			"/inbound/calls/acall?customer_id=customerid_limit",
			{
				from: "+15557654321",
				to: "sip:boop@example.com",
				callId: "ocall",
				digits: "1"
			}.to_json,
			{ "CONTENT_TYPE" => "application/json" }
		)

		assert last_response.ok?
		assert_equal(
			"<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
			"<Tag>connected</Tag><Bridge>acall</Bridge>" \
			"</Response>",
			last_response.body
		)
	end
	em :test_inbound_limit_digits_leg2

	def test_inbound_limit_hangup
		Web::BANDWIDTH_VOICE.expect(
			:modify_call,
			nil,
			[
				"test_bw_account",
				"bcall"
			],
			body: Matching.new do |arg|
				assert_equal(
					"http://example.org/inbound/calls/oocall/voicemail",
					arg.redirect_url
				)
			end
		)

		post(
			"/inbound/calls/bcall/transfer_complete",
			{
				from: "+15557654321",
				to: "+15551234561",
				callId: "oocall",
				cause: "hangup"
			}.to_json,
			{ "CONTENT_TYPE" => "application/json" }
		)

		assert last_response.ok?
		assert_mock Web::BANDWIDTH_VOICE
	end
	em :test_inbound_limit_hangup

	def test_voicemail
		language_id = stub_request(:post, "https://api.rev.ai/languageid/v1/jobs")
			.with(body: {
				metadata: {
					media_url: "https://jmp.chat/media",
					from_jid: "+15557654321@component",
					customer_id: "customerid"
				}.to_json,
				source_config: {
					url: "https://jmp.chat/media"
				},
				notification_config: {
					url: "http://example.org/inbound/calls/CALLID/voicemail/language_id"
				}
			}.to_json)

		Customer::BLATHER.expect(
			:<<,
			nil,
			[Matching.new do |stanza|
				assert_equal "+15557654321@component", stanza.from.to_s
				assert_equal "customer@example.com", stanza.to.to_s
				assert_equal "https://jmp.chat/media", OOB.find_or_create(stanza).url
			end]
		)

		post(
			"/inbound/calls/CALLID/voicemail/audio",
			{
				"startTime" => "2021-01-01T00:00:00Z",
				"endTime" => "2021-01-01T00:00:06Z",
				"mediaUrl" => "https://voice.bandwidth.com/api/v2/accounts/1/media",
				"to" => "+15551234567",
				"from" => "+15557654321"
			}.to_json,
			{ "CONTENT_TYPE" => "application/json" }
		)

		assert last_response.ok?
		assert_mock Customer::BLATHER
		assert_requested language_id
	end
	em :test_voicemail

	def test_anonymous_voicemail
		language_id = stub_request(:post, "https://api.rev.ai/languageid/v1/jobs")
			.with(body: {
				metadata: {
					media_url: "https://jmp.chat/media",
					from_jid:
						"16;phone-context=anonymous.phone-context.soprani.ca@component",
					customer_id: "customerid"
				}.to_json,
				source_config: {
					url: "https://jmp.chat/media"
				},
				notification_config: {
					url: "http://example.org/inbound/calls/CALLID/voicemail/language_id"
				}
			}.to_json)

		Customer::BLATHER.expect(
			:<<,
			nil,
			[Matching.new do |stanza|
				assert_equal(
					"16;phone-context=anonymous.phone-context.soprani.ca@component",
					stanza.from.to_s
				)
				assert_equal "customer@example.com", stanza.to.to_s
				assert_equal "https://jmp.chat/media", OOB.find_or_create(stanza).url
			end]
		)

		post(
			"/inbound/calls/CALLID/voicemail/audio",
			{
				"startTime" => "2021-01-01T00:00:00Z",
				"endTime" => "2021-01-01T00:00:06Z",
				"mediaUrl" => "https://voice.bandwidth.com/api/v2/accounts/1/media",
				"to" => "+15551234567",
				"from" => "Anonymous"
			}.to_json,
			{ "CONTENT_TYPE" => "application/json" }
		)

		assert last_response.ok?
		assert_mock Customer::BLATHER
		assert_requested language_id
	end
	em :test_anonymous_voicemail

	def test_voicemail_short
		post(
			"/inbound/calls/CALLID/voicemail/audio",
			{
				"startTime" => "2021-01-01T00:00:00Z",
				"endTime" => "2021-01-01T00:00:05Z"
			}.to_json,
			{ "CONTENT_TYPE" => "application/json" }
		)

		assert last_response.ok?
		assert_mock Customer::BLATHER
	end
	em :test_voicemail_short

	def test_voicemail_no_customer
		post(
			"/inbound/calls/CALLID/voicemail",
			{
				"startTime" => "2021-01-01T00:00:00Z",
				"endTime" => "2021-01-01T00:00:06Z",
				"mediaUrl" => "https://voice.bandwidth.com/api/v2/accounts/1/media",
				"to" => "+15551200000",
				"from" => "+15557654321"
			}.to_json,
			{ "CONTENT_TYPE" => "application/json" }
		)

		assert last_response.ok?
		assert_equal(
			"<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
			"<SpeakSentence>The number you have dialled is not in service." \
			"</SpeakSentence></Response>",
			last_response.body
		)
	end
	em :test_voicemail_no_customer

	def test_inbound_from_reachability_during_reachability
		ReachableRedis.expect(
			:exists,
			EMPromise.resolve(1),
			["jmp_customer_reachability_voice-customerid_reach"]
		)
		ReachableRedis.expect(
			:incr,
			EMPromise.resolve(1),
			["jmp_customer_reachability_voice-customerid_reach"]
		)

		post(
			"/inbound/calls",
			{
				from: "+14445556666",
				to: "+15551234563",
				callId: "acall"
			}.to_json,
			{ "CONTENT_TYPE" => "application/json" }
		)

		assert last_response.ok?
		assert_equal(
			"<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
			"<Hangup />" \
			"</Response>",
			last_response.body
		)
		assert_mock CustomerFwd::BANDWIDTH_VOICE
		assert_mock ReachableRedis
	end
	em :test_inbound_from_reachability_during_reachability
end
