# frozen_string_literal: true

require "test_helper"
require_relative "../../sgx-bwmsgsv2"
require "rantly/minitest_extensions"
require_relative "generators/webhook"
require_relative "generators/message"

def panic(e)
	$panic = e
end

MMS_PROXY = "https://proxy.test.example.com/"

class WebhookPropertyTest < Minitest::Test
	def setup
		reset_stanzas!
		reset_redis!
	end

	def test_single_recipient_message_delivered_sends_one_receipt
		property_of {
			Webhook
				.new(REDIS)
				.type { "message-delivered" }
				.message { |registered, jid, dir, top_level_to|
					Message
						.new(REDIS)
						.to { [top_level_to] }
						.generate(registered, jid, dir)
				}
				.generate
		}.check { |metadata, example|
			result = invoke_webhook(example)
			assert_equal 200, result[0]
			assert_equal 1, written.length, "Should only send the delivery receipt"
			receipt = written.shift
			assert_equal(
				receipt.to,
				metadata["jid"],
				"Should send receipt to customer's jid"
			)
			assert_equal(
				receipt.from.to_s,
				"#{example["to"]}@#{ARGV[0]}",
				"Should send receipt from sender's Cheogram jid"
			)
		}
	end
	em :test_single_recipient_message_delivered_sends_one_receipt

	def test_multi_recipient_outbound_sends_no_receipts
		property_of {
			Webhook
				.new(REDIS)
				.type { choose(*Webhook::OUTBOUND_TYPES) }
				.message { |registered, jid, dir, top_level_to|
					Message
						.new(REDIS)
						.to {
							array(integer(2)) { nanpa_phone } +
								[top_level_to] +
								array(range(1, 3)) { nanpa_phone }
						}
						.generate(registered, jid, dir)
				}
				.generate
		}.check { |metadata, example|
			result = invoke_webhook(example)
			assert_equal 200, result[0]
			assert_equal 0, written.length, "Should not group chat receipts"
		}
	end
	em :test_multi_recipient_outbound_sends_no_receipts

	def test_single_recipient_message_failed_sends_one_error
		property_of {
			Webhook
				.new(REDIS)
				.type { "message-failed" }
				.message { |registered, jid, dir, top_level_to|
					Message
						.new(REDIS)
						.to { [top_level_to] }
						.generate(registered, jid, dir)
				}
				.generate
		}.check { |metadata, example|
			result = invoke_webhook(example)
			assert_equal 200, result[0]
			assert_equal(
				written.length,
				1,
				"Message failed should only send one notification"
			)
			assert_kind_of(
				::Blather::StanzaError,
				written.shift,
				"Should not notify message-failed"
			)
		}
	end
	em :test_single_recipient_message_failed_sends_one_error

	def test_outbound_unregistered_returns_403
		property_of {
			Webhook
				.new(REDIS)
				.registered {
					false
				}
				.type {
					choose(*Webhook::OUTBOUND_TYPES)
				}
				.generate
		}.check { |metadata, example|
			result = invoke_webhook(example)
			assert_equal [403, {}, "Customer not found\n"], result
			assert_empty written
			entries = REDIS.stream_entries("messages").sync
			assert_empty entries
		}
	end
	em :test_outbound_unregistered_returns_403

	def test_unknown_outbound_returns_200
		property_of {
			Webhook
				.new(REDIS)
				.type { "unknown" }
				.direction { "out" }
				.generate
		}.check { |metadata, example|
			result = invoke_webhook(example)
			assert_equal [200, {}, "OK"], result
			assert_empty written
			entries = REDIS.stream_entries("messages").sync
			assert_empty entries
		}
	end
	em :test_unknown_outbound_returns_200

	def test_unknown_direction_returns_400_ok_except_when_message_failed
		property_of {
			Webhook
				.new(REDIS)
				.direction { "unknown" }
				.type {
					choose(*(Webhook::INBOUND_TYPES << Webhook::OUTBOUND_TYPES)
							.reject { |ty|
								ty == "message-failed"
							}
					)
				}
				.generate
		}.check { |metadata, example|
			result = invoke_webhook(example)
			assert_equal [400, {}, "OK"], result
			assert_empty written
			entries = REDIS.stream_entries("messages").sync
			assert_empty entries
		}
	end
	em :test_unknown_direction_returns_400_ok_except_when_message_failed

	def test_unknown_direction_returns_400_missing_params_when_message_failed
		property_of {
			Webhook
				.new(REDIS)
				.direction { "unknown" }
				.type {
					"message-failed"
				}
				.generate
		}.check { |metadata, example|
			result = invoke_webhook(example)
			assert_equal [400, {}, "Missing params\n"], result
			assert_empty written
			entries = REDIS.stream_entries("messages").sync
			assert_empty entries
		}
	end
	em :test_unknown_direction_returns_400_missing_params_when_message_failed

	def test_delivered_emits_correct_stream_event
		property_of {
			Webhook
				.new(REDIS)
				.type { "message-delivered" }
				.generate
		}.check { |metadata, example|
			invoke_webhook(example)

			entries = REDIS.stream_entries("messages").sync
			assert_equal 1, entries.length

			fields = entries.first[:fields]
			expected_keys = %w[
				event source timestamp stanza_id bandwidth_id
			].sort
			assert_equal expected_keys, fields.keys.sort

			assert_equal "delivered", fields["event"]
			assert_equal "bwmsgsv2", fields["source"]

			assert_equal metadata["stanza_id"], fields["stanza_id"]
			assert_equal example["message"]["id"], fields["bandwidth_id"]
			assert_equal example["message"]["time"], fields["timestamp"]
		}
	end
	em :test_delivered_emits_correct_stream_event

	def test_failed_emits_correct_stream_event
		property_of {
			Webhook
				.new(REDIS)
				.type { "message-failed" }
				.generate
		}.check { |metadata, example|
			invoke_webhook(example)

			entries = REDIS.stream_entries("messages").sync
			assert_equal 1, entries.length

			fields = entries.first[:fields]
			expected_keys = %w[
				event source timestamp stanza_id bandwidth_id
				error_code error_description
			].sort
			assert_equal expected_keys, fields.keys.sort

			assert_equal "failed", fields["event"]
			assert_equal "bwmsgsv2", fields["source"]

			tag_parts = example["message"]["tag"].split(/ /, 2)
			expected_stanza_id = WEBrick::HTTPUtils.unescape(tag_parts[0])
			assert_equal expected_stanza_id, fields["stanza_id"]
			assert_equal example["message"]["id"], fields["bandwidth_id"]
			assert_equal example["message"]["time"], fields["timestamp"]
			assert_equal example["message"]["errorCode"].to_s, fields["error_code"]
			assert_equal example["message"]["description"].to_s, fields["error_description"]
		}
	end
	em :test_failed_emits_correct_stream_event

	def test_inbound_received_emits_correct_in_stream_event
		property_of {
			Webhook
				.new(REDIS)
				.message { |registered, jid, dir, top_level_to|
					Message
						.new(REDIS)
						.to {
							array(integer(2)) { nanpa_phone } +
								[top_level_to] +
								array(integer(2)) { nanpa_phone }
						}
						.generate(registered, jid, dir)
				}
				.type { "message-received" }
				.generate
		}.check { |metadata, example|
			result = invoke_webhook(example)
			assert_equal 200, result[0]

			entries = REDIS.stream_entries("messages").sync
			assert_equal 1, entries.length

			fields = entries.first[:fields]
			expected_keys = %w[
				event source timestamp owner from to
				bandwidth_id body media_urls
			].sort
			assert_equal expected_keys, fields.keys.sort

			assert_equal "in", fields["event"]
			assert_equal "bwmsgsv2", fields["source"]
			assert_equal example["message"]["time"], fields["timestamp"]
			assert_equal example["message"]["owner"], fields["owner"]
			assert_equal example["message"]["from"], fields["from"]
			assert_equal JSON.dump(example["message"]["to"]), fields["to"]
			assert_equal example["message"]["id"], fields["bandwidth_id"]
			assert_equal example["message"]["text"].to_s, fields["body"]

			expected_media = Array(example["message"]["media"]).reject { |u|
				u.end_with?(".smil", ".txt", ".xml")
			}
			assert_equal JSON.dump(expected_media), fields["media_urls"]
		}
	end
	em :test_inbound_received_emits_correct_in_stream_event

	def test_inbound_resend_emits_correct_resend_stream_event
		property_of {
			Webhook
				.new(REDIS)
				.type { "message-received" }
				.message { |registered, jid, dir, top_level_to|
					Message
						.new(REDIS)
						.to {
							array(integer(2)) { nanpa_phone } +
								[top_level_to] +
								array(integer(2)) { nanpa_phone }
						}
						.generate(registered, jid, dir)
				}
				.generate
		}.check { |metadata, example|
			result = invoke_webhook(
				example,
				extra_env: { "HTTP_X_JMP_RESEND_OF" => metadata["resend_id"] }
			)
			assert_equal 200, result[0]

			entries = REDIS.stream_entries("messages").sync
			assert_equal 1, entries.length

			fields = entries.first[:fields]
			expected_keys = %w[
				event source original_stream_id owner
				original_bandwidth_id
			].sort
			assert_equal expected_keys, fields.keys.sort

			assert_equal "resend", fields["event"]
			assert_equal "bwmsgsv2", fields["source"]
			assert_equal metadata["resend_id"], fields["original_stream_id"]
			assert_equal example["message"]["owner"], fields["owner"]
			assert_equal example["message"]["id"], fields["original_bandwidth_id"]
		}
	end
	em :test_inbound_resend_emits_correct_resend_stream_event

	def test_inbound_empty_text_no_media_returns_400
		property_of {
			Webhook
				.new(REDIS)
				.type { "message-received" }
				.message { |registered, jid, dir, _|
					Message
						.new(REDIS)
						.to { [nanpa_phone] }
						.text { "" }
						.media { nil }
						.generate(registered, jid, dir)
				}
				.generate
		}.check { |metadata, example|
			result = invoke_webhook(example)
			assert_equal [400, {}, "Missing params\n"], result
			assert_empty written
			entries = REDIS.stream_entries("messages").sync
			assert_empty entries
		}
	end
	em :test_inbound_empty_text_no_media_returns_400

	def test_inbound_unknown_type_sends_notification
		property_of {
			Webhook
				.new(REDIS)
				.type { "unknown" }
				.direction { "in" }
				.message { |registered, jid, dir, top_level_to|
					Message
						.new(REDIS)
						.to { [nanpa_phone] }
						.generate(registered, jid, dir)
				}
				.generate
		}.check { |metadata, example|
			result = invoke_webhook(example)
			assert_equal 200, result[0]
			assert_equal 1, written.length

			msg = written.shift
			assert_kind_of Blather::Stanza::Message, msg
			assert_equal(
				metadata["jid"],
				msg.to.to_s,
				"Notification should be sent to customer's jid"
			)

			expected_from = example["message"]["from"]
			unless expected_from.start_with?("+")
				expected_from += ";phone-context=ca-us.phone-context.soprani.ca"
			end
			assert_equal(
				"#{expected_from}@#{ARGV[0]}",
				msg.from.to_s,
				"Notification should be from sender's Cheogram jid"
			)

			expected_text = "unknown type (unknown)" \
				" with text: #{example["message"]["text"]}"
			assert_equal(
				expected_text,
				msg.body,
				"Body should contain the unknown type and original text"
			)

			entries = REDIS.stream_entries("messages").sync
			assert_empty entries, "Unknown inbound type should not emit a stream event"
		}
	end
	em :test_inbound_unknown_type_sends_notification

	def test_request_with_empty_params_produces_no_output
		property_of {
			Webhook.new(REDIS).generate
		}.check { |metadata, example|
			result = invoke_webhook(example, extra_env: { "params" => {} })
			assert_equal [200, {}, "OK"], result
			assert_empty written
		}
	end
	em :test_request_with_empty_params_produces_no_output

	def test_request_with_non_root_uri_produces_no_output
		property_of {
			Webhook.new(REDIS).generate
		}.check { |metadata, example|
			result = invoke_webhook(example, extra_env: { "REQUEST_URI" => "/wrong" })
			assert_equal [200, {}, "OK"], result
			assert_empty written
		}
	end
	em :test_request_with_non_root_uri_produces_no_output

	def test_request_with_non_post_method_produces_no_output
		property_of {
			Webhook.new(REDIS).generate
		}.check { |metadata, example|
			result = invoke_webhook(example, extra_env: { "REQUEST_METHOD" => "GET" })
			assert_equal [200, {}, "OK"], result
			assert_empty written
		}
	end
	em :test_request_with_non_post_method_produces_no_output

	def test_payload_without_message_or_type_returns_400
		property_of {
			metadata, example = Webhook.new(REDIS).generate
			[choose("message", "type"), metadata, example]
		}.check { |key, metadata, example|
			example.delete(key)
			result = invoke_webhook(example)
			assert_equal [400, {}, "Missing params\n"], result
			assert_empty written
		}
	end
	em :test_payload_without_message_or_type_returns_400

	def test_message_with_non_array_to_returns_400
		property_of {
			Webhook
				.new(REDIS)
				.message { |registered, jid, dir, top_level_to|
					Message
						.new(REDIS)
						.to { top_level_to }
						.owner { top_level_to }
						.generate(registered, jid, dir)
				}
				.generate
		}.check { |metadata, example|
			result = invoke_webhook(example)
			assert_equal [400, {}, "Missing params\n"], result
			assert_empty written
		}
	end
	em :test_message_with_non_array_to_returns_400

	def test_message_with_empty_to_returns_400
		property_of {
			Webhook
				.new(REDIS)
				.message { |registered, jid, dir, top_level_to|
					Message
						.new(REDIS)
						.to { [] }
						.owner { top_level_to }
						.generate(registered, jid, dir)
				}
				.generate
		}.check { |metadata, example|
			result = invoke_webhook(example)
			assert_equal [400, {}, "Missing params\n"], result
			assert_empty written
		}
	end
	em :test_message_with_empty_to_returns_400

	def test_inbound_nil_text_with_media_multi_recipient_writes_empty_body
		property_of {
			Webhook
				.new(REDIS)
				.type { "message-received" }
				.message { |registered, jid, dir, top_level_to|
					Message
						.new(REDIS)
						.to {
							array(range(1, 3)) { nanpa_phone } +
								[top_level_to] +
								array(integer(2)) { nanpa_phone }
						}
						.text { nil }
						.media { array(range(1, 3)) { media_url } }
						.generate(registered, jid, dir)
				}
				.generate
		}.check { |metadata, example|
			result = invoke_webhook(example)
			assert_equal 200, result[0]
			assert_operator written.length, :>=, 1

			msg = written.last
			assert_kind_of Blather::Stanza::Message, msg
			assert(
				msg.body.to_s.empty?,
				"Body should be nil/empty when text is nil"
			)
		}
	end
	em :test_inbound_nil_text_with_media_multi_recipient_writes_empty_body
end
