# frozen_string_literal: true

require "test_helper"
require_relative "../sgx-bwmsgsv2"

def panic(e)
	$panic = e
end

class ComponentTest < Minitest::Test
	def setup
		SGXbwmsgsv2.instance_variable_set(:@written, [])

		def SGXbwmsgsv2.write_to_stream(s)
			@written ||= []
			@written << s
		end

		REDIS.reset!
		REDIS.set("catapult_jid-", "HERE")
		REDIS.set("catapult_jid-+15550000000", "test@example.com")
		REDIS.set("catapult_cred-test@example.com", [
			'account', 'user', 'password', '+15550000000'
		])
	end

	def written
		SGXbwmsgsv2.instance_variable_get(:@written)
	end

	def xmpp_error_name(error)
		error.find_first(
			"child::*[name()!='text']",
			Blather::StanzaError::STANZA_ERR_NS
		).element_name
	end

	def xmpp_error_text(error)
		error.find_first("ns:text", ns: Blather::StanzaError::STANZA_ERR_NS)&.text
	end

	def process_stanza(s)
		SGXbwmsgsv2.send(:client).receive_data(s)
		raise $panic if $panic
	end

	def test_message_unregistered
		m = Blather::Stanza::Message.new("+15551234567@component", "a"*4096)
		m.from = "unknown@example.com"
		process_stanza(m)

		assert_equal 1, written.length

		stanza = Blather::XMPPNode.parse(written.first.to_xml)
		assert stanza.error?
		error = stanza.find_first("error")
		assert_equal "auth", error["type"]
		assert_equal "registration-required", xmpp_error_name(error)
	end
	em :test_message_unregistered

	def test_message_too_long
		req = stub_request(
			:post,
			"https://messaging.bandwidth.com/api/v2/users/account/messages"
		).with(body: {
			from: "+15550000000",
			to: "+15551234567",
			text: "a"*4096,
			applicationId: nil,
			tag: " "
		}).to_return(status: 400, body: JSON.dump(
			description: "Bad text.",
			fieldErrors: [{ description: "4096 not allowed" }]
		))

		m = Blather::Stanza::Message.new("+15551234567@component", "a"*4096)
		m.from = "test@example.com"
		process_stanza(m)

		assert_requested req
		assert_equal 1, written.length

		stanza = Blather::XMPPNode.parse(written.first.to_xml)
		assert stanza.error?
		error = stanza.find_first("error")
		assert_equal "cancel", error["type"]
		assert_equal "internal-server-error", xmpp_error_name(error)
		assert_equal "Bad text. 4096 not allowed", xmpp_error_text(error)
	end
	em :test_message_too_long

	def test_message_to_component_not_group
		m = Blather::Stanza::Message.new("component", "a"*4096)
		m.from = "test@example.com"
		process_stanza(m)

		assert_equal 1, written.length

		stanza = Blather::XMPPNode.parse(written.first.to_xml)
		assert stanza.error?
		error = stanza.find_first("error")
		assert_equal "cancel", error["type"]
		assert_equal "item-not-found", xmpp_error_name(error)
	end
	em :test_message_to_component_not_group

	def test_message_to_invalid_num
		m = Blather::Stanza::Message.new("123@component", "a"*4096)
		m.from = "test@example.com"
		process_stanza(m)

		assert_equal 1, written.length

		stanza = Blather::XMPPNode.parse(written.first.to_xml)
		assert stanza.error?
		error = stanza.find_first("error")
		assert_equal "cancel", error["type"]
		assert_equal "item-not-found", xmpp_error_name(error)
	end
	em :test_message_to_invalid_num

	def test_message_to_anonymous
		m = Blather::Stanza::Message.new(
			"1;phone-context=anonymous.phone-context.soprani.ca@component",
			"a"*4096
		)
		m.from = "test@example.com"
		process_stanza(m)

		assert_equal 1, written.length

		stanza = Blather::XMPPNode.parse(written.first.to_xml)
		assert stanza.error?
		error = stanza.find_first("error")
		assert_equal "cancel", error["type"]
		assert_equal "gone", xmpp_error_name(error)
	end
	em :test_message_to_anonymous

	def test_blank_message
		m = Blather::Stanza::Message.new("+15551234567@component", " ")
		m.from = "test@example.com"
		process_stanza(m)

		assert_equal 1, written.length

		stanza = Blather::XMPPNode.parse(written.first.to_xml)
		assert stanza.error?
		error = stanza.find_first("error")
		assert_equal "modify", error["type"]
		assert_equal "policy-violation", xmpp_error_name(error)
	end
	em :test_blank_message

	def test_ibr_bad_tel
		iq = Blather::Stanza::Iq::IBR.new(:set, "component")
		iq.from = "newuser@example.com"
		iq.phone = "5551234567"
		process_stanza(iq)

		assert_equal 1, written.length

		stanza = Blather::XMPPNode.parse(written.first.to_xml)
		assert stanza.error?
		error = stanza.find_first("error")
		assert_equal "cancel", error["type"]
		assert_equal "item-not-found", xmpp_error_name(error)
	end
	em :test_ibr_bad_tel

	def test_ibr_bad_creds
		stub_request(
			:get,
			"https://messaging.bandwidth.com/api/v2/users/acct/media"
		).with(basic_auth: ["user", "pw"]).to_return(status: 401)

		iq = Blather::Stanza::Iq::IBR.new(:set, "component")
		iq.from = "newuser@example.com"
		iq.phone = "+15551234567"
		iq.nick = "acct"
		iq.username = "user"
		iq.password = "pw"
		process_stanza(iq)

		assert_equal 1, written.length

		stanza = Blather::XMPPNode.parse(written.first.to_xml)
		assert stanza.error?
		error = stanza.find_first("error")
		assert_equal "auth", error["type"]
		assert_equal "not-authorized", xmpp_error_name(error)
	end
	em :test_ibr_bad_creds

	def test_ibr_number_not_found
		stub_request(
			:get,
			"https://messaging.bandwidth.com/api/v2/users/acct/media"
		).with(basic_auth: ["user", "pw"]).to_return(status: 404)

		iq = Blather::Stanza::Iq::IBR.new(:set, "component")
		iq.from = "newuser@example.com"
		iq.phone = "+15551234567"
		iq.nick = "acct"
		iq.username = "user"
		iq.password = "pw"
		process_stanza(iq)

		assert_equal 1, written.length

		stanza = Blather::XMPPNode.parse(written.first.to_xml)
		assert stanza.error?
		error = stanza.find_first("error")
		assert_equal "cancel", error["type"]
		assert_equal "item-not-found", xmpp_error_name(error)
	end
	em :test_ibr_number_not_found

	def test_ibr_other_error
		stub_request(
			:get,
			"https://messaging.bandwidth.com/api/v2/users/acct/media"
		).with(basic_auth: ["user", "pw"]).to_return(status: 400)

		iq = Blather::Stanza::Iq::IBR.new(:set, "component")
		iq.from = "newuser@example.com"
		iq.phone = "+15551234567"
		iq.nick = "acct"
		iq.username = "user"
		iq.password = "pw"
		process_stanza(iq)

		assert_equal 1, written.length

		stanza = Blather::XMPPNode.parse(written.first.to_xml)
		assert stanza.error?
		error = stanza.find_first("error")
		assert_equal "modify", error["type"]
		assert_equal "not-acceptable", xmpp_error_name(error)
	end
	em :test_ibr_other_error

	def test_ibr_new
		stub_request(
			:get,
			"https://messaging.bandwidth.com/api/v2/users/acct/media"
		).with(basic_auth: ["user", "pw"]).to_return(status: 200, body: "[]")

		iq = Blather::Stanza::Iq::IBR.new(:set, "component")
		iq.from = "test9@example.com"
		iq.phone = "+15550000009"
		iq.nick = "acct"
		iq.username = "user"
		iq.password = "pw"
		process_stanza(iq)

		assert_equal 1, written.length

		stanza = Blather::XMPPNode.parse(written.first.to_xml)
		refute stanza.error?
		assert_equal(
			["acct", "user", "pw", "+15550000009"],
			REDIS.get("catapult_cred-test9@example.com").sync
		)
		assert_equal "test9@example.com", REDIS.get("catapult_jid-+15550000009").sync
		assert REDIS.get("catapult_jid-").sync
	end
	em :test_ibr_new

	def test_ibr_update
		stub_request(
			:get,
			"https://messaging.bandwidth.com/api/v2/users/acct/media"
		).with(basic_auth: ["user", "pw"]).to_return(status: 200, body: "[]")

		iq = Blather::Stanza::Iq::IBR.new(:set, "component")
		iq.from = "test@example.com"
		iq.phone = "+15550000009"
		iq.nick = "acct"
		iq.username = "user"
		iq.password = "pw"
		process_stanza(iq)

		assert_equal 1, written.length

		stanza = Blather::XMPPNode.parse(written.first.to_xml)
		refute stanza.error?
		assert_equal(
			["acct", "user", "pw", "+15550000009"],
			REDIS.get("catapult_cred-test@example.com").sync
		)
		assert_equal "test@example.com", REDIS.get("catapult_jid-+15550000009").sync
		refute REDIS.get("catapult_jid-+15550000000").sync
	end
	em :test_ibr_update

	def test_ibr_conflict
		stub_request(
			:get,
			"https://messaging.bandwidth.com/api/v2/users/acct/media"
		).with(basic_auth: ["user", "pw"]).to_return(status: 200, body: "[]")

		iq = Blather::Stanza::Iq::IBR.new(:set, "component")
		iq.from = "test2@example.com"
		iq.phone = "+15550000000"
		iq.nick = "acct"
		iq.username = "user"
		iq.password = "pw"
		process_stanza(iq)

		assert_equal 1, written.length

		stanza = Blather::XMPPNode.parse(written.first.to_xml)
		assert stanza.error?
		error = stanza.find_first("error")
		assert_equal "cancel", error["type"]
		assert_equal "conflict", xmpp_error_name(error)
		assert_equal(
			"Another user exists for +15550000000",
			xmpp_error_text(error)
		)
	end
	em :test_ibr_conflict

	def test_ibr_remove
		iq = Blather::Stanza::Iq::IBR.new(:set, "component")
		iq.from = "test@example.com"
		iq.remove!
		process_stanza(iq)

		refute REDIS.get("catapult_cred-test@example.com").sync

		assert_equal 1, written.length

		stanza = Blather::XMPPNode.parse(written.first.to_xml)
		assert stanza.result?
	end
	em :test_ibr_remove

	def test_ibr_form
		stub_request(
			:get,
			"https://messaging.bandwidth.com/api/v2/users/acct/media"
		).with(basic_auth: ["user", "pw"]).to_return(status: 200, body: "[]")

		iq = Blather::Stanza::Iq::IBR.new(:set, "component")
		iq.from = "formuser@example.com"
		form = Blather::Stanza::X.find_or_create(iq.query)
		form.fields = [
			{
				var: "nick",
				value: "acct"
			},
			{
				var: "username",
				value: "user"
			},
			{
				var: "password",
				value: "pw"
			},
			{
				var: "phone",
				value: "+15551234567"
			}
		]
		process_stanza(iq)

		assert_equal(
			["acct", "user", "pw", "+15551234567"],
			REDIS.get("catapult_cred-formuser@example.com").sync
		)

		assert_equal(
			"formuser@example.com",
			REDIS.get("catapult_jid-+15551234567").sync
		)

		assert_equal 1, written.length
		stanza = Blather::XMPPNode.parse(written.first.to_xml)
		assert stanza.result?
	end
	em :test_ibr_form

	def test_ibr_get_form_registered
		iq = Blather::Stanza::Iq::IBR.new(:get, "component")
		iq.from = "test@example.com"
		process_stanza(iq)

		assert_equal 1, written.length
		stanza = Blather::XMPPNode.parse(written.first.to_xml)
		assert stanza.result?
		assert stanza.registered?
		assert_equal(
			["nick", "username", "password", "phone"],
			stanza.form.fields.map(&:var)
		)
		assert stanza.instructions
		assert stanza.nick
		assert stanza.username
		assert stanza.password
		assert stanza.phone
		refute stanza.email
	end
	em :test_ibr_get_form_registered

	def test_port_out_pin
		iq = Blather::Stanza::Iq::Command.new(:set, 'component').tap do |iq|
			iq.from = 'test@example.com'
			iq.node = 'set-port-out-pin'
			iq.sessionid = 'test-session-123'
			iq.action = :complete
			iq.form.type = :submit
			iq.form.fields = [
				{
					var: 'pin',
					value: '1234'
				},
				{
					var: 'confirm_pin',
					value: '1234'
				}
			]
		end

		BandwidthIris::TnOptions.stub :create_tn_option_order,
			->(client, data) { {order_id: 'test-order-123', processing_status: 'RECEIVED', error_list: {}} } do
			BandwidthIris::TnOptions.stub :get_tn_option_order,
				->(client, order_id) { {order_id: order_id, order_status: 'COMPLETE', error_list: {}} } do

				process_stanza(iq)

				assert_equal 1, written.length

				stanza = Blather::XMPPNode.parse(written.first.to_xml)
				refute stanza.error?
			end
		end
	end
	em :test_port_out_pin

	def test_port_out_pin_mismatch
		iq = Blather::Stanza::Iq::Command.new(:set, 'component').tap do |iq|
			iq.from = 'test@example.com'
			iq.node = 'set-port-out-pin'
			iq.sessionid = 'test-session-mismatch'
			iq.action = :complete
			iq.form.type = :submit
			iq.form.fields = [
				{
					var: 'pin',
					value: '1234'
				},
				{
					var: 'confirm_pin',
					value: '5678'
				}
			]
		end

		process_stanza(iq)

		assert_equal 1, written.length

		stanza = Blather::XMPPNode.parse(written.first.to_xml)
		assert_equal :error, stanza.type
		error = stanza.find_first("error")
		assert_equal "modify", error["type"]
		assert_equal "bad-request", xmpp_error_name(error)
		assert_equal "PIN confirmation does not match", xmpp_error_text(error)
	end
	em :test_port_out_pin_mismatch

	def test_port_out_pin_validation
		[
			['123', 'PIN must be 4-10 alphanumeric characters'],
			['12345678901', 'PIN must be 4-10 alphanumeric characters'],
			['123!', 'PIN must be 4-10 alphanumeric characters'],
			['pin with spaces', 'PIN must be 4-10 alphanumeric characters']
		].each do |invalid_pin, expected_error|
			iq = Blather::Stanza::Iq::Command.new(:set, 'component').tap do |iq|
				iq.from = 'test@example.com'
				iq.node = 'set-port-out-pin'
				iq.sessionid = "test-session-validation-#{invalid_pin.gsub(/[^a-zA-Z0-9]/, '')}"
				iq.action = :complete
				iq.form.type = :submit
				iq.form.fields = [
					{
						var: 'pin',
						value: invalid_pin
					},
					{
						var: 'confirm_pin',
						value: invalid_pin
					}
				]
			end

			process_stanza(iq)

			assert_equal 1, written.length, "Failed for PIN: #{invalid_pin}"

			stanza = Blather::XMPPNode.parse(written.first.to_xml)
			assert_equal :error, stanza.type, "Expected error for PIN: #{invalid_pin}"
			error = stanza.find_first("error")
			assert_equal "modify", error["type"]
			assert_equal "bad-request", xmpp_error_name(error)
			assert_equal expected_error, xmpp_error_text(error),
			             "Wrong error message for PIN: #{invalid_pin}"

			SGXbwmsgsv2.instance_variable_set(:@written, [])
		end
	end
	em :test_port_out_pin_validation
end
