# frozen_string_literal: true

require "test_helper"
require "admin_command"
require "admin_action"

BackendSgx::IQ_MANAGER = Minitest::Mock.new
AdminAction::LaunchSnikket::IQ_MANAGER = Minitest::Mock.new
Customer::BLATHER = Minitest::Mock.new
AdminActionRepo::REDIS = Minitest::Mock.new
TrivialBackendSgxRepo::REDIS = Minitest::Mock.new
BandwidthTnRepo::DB = Minitest::Mock.new
AdminAction::NumberChange::REDIS = Minitest::Mock.new

class AdminCommandTest < Minitest::Test
	def assert_undoable_form(iq, note_type=nil, note_text=nil)
		assert_equal :form, iq.form.type
		[:execute, :next, :complete].each do |action|
			assert iq.allowed_actions.include?(action)
		end
		assert_equal iq.note_type, note_type if note_type

		assert iq.note_text = note_text if note_text
	end

	def admin_command(tel="+15556667777", registered: OpenStruct.new(phone: tel))
		sgx = Minitest::Mock.new(OpenStruct.new(
			registered?: registered
		))
		[
			sgx,
			AdminCommand.new(
				customer(sgx: sgx),
				CustomerRepo.new(db: FakeDB.new),
				AdminActionRepo.new,
				Snikket::Repo.new(db: FakeDB.new)
			)
		]
	end

	def test_no_user
		q_form = Blather::Stanza::Iq::Command.new
		q_form.action = :complete
		q_form.form.fields = [
			{ var: "q", value: "testuser" }
		]

		customer_repo = Minitest::Mock.new

		result = execute_command {
			customer_repo.expect(
				:find_by_format,
				EMPromise.resolve(OpenStruct.new(
					customer_id: "testuser",
					billing_customer_id: "testuser",
					balance: 0.to_d,
					jid: Blather::JID.new("test@example.com"),
					tndetails: {}
				)),
				["testuser"]
			)

			customer_repo.expect(
				:find_by_format,
				EMPromise.resolve(nil),
				[Blather::JID]
			)

			customer_repo.expect(
				:find_by_format,
				EMPromise.resolve(nil),
				[ProxiedJID]
			)

			TrivialBackendSgxRepo::REDIS.expect(
				:get,
				EMPromise.resolve(nil),
				["jmp_customer_backend_sgx-testuser"]
			)

			TrustLevelRepo::REDIS.expect(
				:get,
				EMPromise.resolve("Customer"),
				["jmp_customer_trust_level-testuser"]
			)

			TrustLevelRepo::DB.expect(
				:query_one,
				EMPromise.resolve({ settled_amount: 0 }),
				[String, "testuser"], default: {}
			)

			Subaccount::DB.expect(
				:query_defer,
				EMPromise.resolve([]),
				[String, ["testuser"]]
			)

			Command::COMMAND_MANAGER.expect(
				:write,
				EMPromise.resolve(q_form),
				[Matching.new do |iq|
					assert_equal :form, iq.form.type
					assert iq.form.field("q")
				end]
			)

			AdminCommand.for(nil, customer_repo).start.catch { |e| e }
		}

		assert result.stanza.completed?
		assert_mock customer_repo
		assert_mock Command::COMMAND_MANAGER
		assert_mock TrustLevelRepo::REDIS
		assert_mock Subaccount::DB
		assert_mock TrivialBackendSgxRepo::REDIS
	end
	em :test_no_user

	def test_action_launch_snikket
		sgx, admin = admin_command
		domain_form = Blather::Stanza::Iq::Command.new
		domain_form.form.fields = [
			{ var: "domain", value: "test.snikket.chat" }
		]

		launched = Snikket::Launched.new
		launched << Niceogiri::XML::Node.new(
			:launched, launched.document, "xmpp:snikket.org/hosting/v1"
		).tap { |inner|
			inner << Niceogiri::XML::Node.new(
				:'instance-id', launched.document, "xmpp:snikket.org/hosting/v1"
			).tap { |id|
				id.content = "si-1234"
			}
			inner << Niceogiri::XML::Node.new(
				:bootstrap, launched.document, "xmpp:snikket.org/hosting/v1"
			).tap { |bootstrap|
				bootstrap << Niceogiri::XML::Node.new(
					:token, launched.document, "xmpp:snikket.org/hosting/v1"
				).tap { |token|
					token.content = "TOKEN"
				}
			}
		}

		result = execute_command {
			Command::COMMAND_MANAGER.expect(
				:write,
				EMPromise.resolve(domain_form),
				[Matching.new do |iq|
					assert_equal :form, iq.form.type
					assert iq.form.field("domain")
				end]
			)
			Command::COMMAND_MANAGER.expect(
				:write,
				EMPromise.reject(:test_result),
				[Matching.new do |iq|
					assert :result, iq.type
					assert(
						"https://test.snikket.chat/invites_bootstrap?token=TOKEN",
						iq.form.field("bootstrap-uri").value
					)
				end]
			)

			AdminAction::LaunchSnikket::IQ_MANAGER.expect(
				:write,
				EMPromise.resolve(launched),
				[Matching.new do |iq|
					assert_equal :set, iq.type
					assert_equal CONFIG[:snikket_hosting_api], iq.to.to_s
					assert_equal(
						"test.snikket.chat",
						iq.xpath(
							"./ns:launch/ns:domain",
							ns: "xmpp:snikket.org/hosting/v1"
						).text
					)
				end]
			)

			admin.action_launch_snikket.catch { |e| e }
		}

		assert_equal :test_result, result
		assert_mock sgx
		assert_mock AdminAction::LaunchSnikket::IQ_MANAGER
		assert_mock Customer::BLATHER
	end
	em :test_action_launch_snikket

	def test_action_cancel_account
		req = stub_request(:post, "https://api.churnbuster.io/v1/cancellations")
			.with(
				body: {
					customer: {
						source: "braintree",
						source_id: "test",
						email: "test@smtp.cheogram.com",
						properties: {}
					},
					subscription: {
						source: "braintree",
						source_id: "test"
					}
				}.to_json
			)
			.to_return(status: 200, body: "", headers: {})

		sgx, admin = admin_command

		Customer::BLATHER.expect(
			:<<,
			EMPromise.resolve(nil),
			[
				Matching.new do |m|
					assert_equal "Your JMP account has been cancelled.", m.body
					assert_equal "test@example.net", m.to.to_s
					assert_equal "notify_from@component", m.from.to_s
				end
			]
		)

		Customer::BLATHER.expect(
			:<<,
			EMPromise.resolve(nil),
			[
				Matching.new do |iq|
					assert iq.remove?
					assert_equal "test@example.net", iq.to.to_s
					assert_equal "component", iq.from.to_s
				end
			]
		)

		sgx.expect(:deregister!, EMPromise.resolve(nil))

		stub_request(
			:post,
			"https://dashboard.bandwidth.com/v1.0/accounts//disconnects"
		).with(
			body: {
				name: "test",
				DisconnectTelephoneNumberOrderType: {
					TelephoneNumberList: {
						TelephoneNumber: "5556667777"
					}
				}
			}.to_xml(indent: 0, root: "DisconnectTelephoneNumberOrder")
		).to_return(status: 200, body: "")

		admin.action_cancel_account.sync

		assert_mock sgx
		assert_mock BackendSgx::IQ_MANAGER
		assert_mock Customer::BLATHER
		assert_requested req
	end
	em :test_action_cancel_account

	def test_action_cancel_account_keep_number
		rate_center_response = {
			"TelephoneNumberDetails": {
				State: "NY",
				RateCenter: "NWYRCYZN01"
			}
		}.to_xml(indent: 0, root: "TelephoneNumberResponse")

		bandwidth_req = stub_request(
			:get,
			"https://dashboard.bandwidth.com/v1.0/tns/5566667777/ratecenter"
		)
			.with(
				headers: {
					"Accept" => "application/xml",
					"Accept-Encoding" => "gzip, compressed",
					"Authorization" => "Basic dGVzdF9id191c2VyOnRlc3RfYndfcGFzc3dvcmQ=",
					"User-Agent" => "Ruby-Bandwidth-Iris"
				},
				body: ""
			)
			.to_return(status: 200, body: rate_center_response, headers: {})

		churnbuster_req = stub_request(
			:post,
			"https://api.churnbuster.io/v1/cancellations"
		)
			.with(
				body: {
					customer: {
						source: "braintree",
						source_id: "test",
						email: "test@smtp.cheogram.com",
						properties: {}
					},
					subscription: {
						source: "braintree",
						source_id: "test"
					}
				}.to_json
			)
			.to_return(status: 200, body: "", headers: {})

		sgx, admin = admin_command("+15566667777")

		sql_params = ["+15566667777", "NY", "NWYRCYZN01", "test_bw_account"]

		BandwidthTnRepo::DB.expect(
			:exec,
			EMPromise.resolve(nil),
			[String, sql_params]
		)

		Customer::BLATHER.expect(
			:<<,
			EMPromise.resolve(nil),
			[
				Matching.new do |m|
					assert_equal "Your JMP account has been cancelled.", m.body
					assert_equal "test@example.net", m.to.to_s
					assert_equal "notify_from@component", m.from.to_s
				end
			]
		)

		Customer::BLATHER.expect(
			:<<,
			EMPromise.resolve(nil),
			[
				Matching.new do |iq|
					assert iq.remove?
					assert_equal "test@example.net", iq.to.to_s
					assert_equal "component", iq.from.to_s
				end
			]
		)

		sgx.expect(:deregister!, EMPromise.resolve(nil))

		admin.action_cancel_account.sync

		assert_mock sgx
		assert_mock BackendSgx::IQ_MANAGER
		assert_mock Customer::BLATHER
		assert_mock BandwidthTnRepo::DB

		assert_requested churnbuster_req
		assert_requested bandwidth_req
	end
	em :test_action_cancel_account_keep_number

	def test_change_num_for_unregistered_customer
		_sgx, admin = admin_command(registered: false)

		error =
			assert_raises("number_change should have raised") {
				admin.action_number_change.sync
			}

		assert_equal "Customer not registered", error.message

		assert_mock BackendSgx::IQ_MANAGER
		assert_mock Customer::BLATHER
		assert_mock BandwidthTnRepo::DB
	end
	em :test_change_num_for_unregistered_customer
end
