# frozen_string_literal: true

require "date"
require "ostruct"
require "test_helper"

require "customer_info"
require "form_template"
require "form_to_h"
require "porting_step"

MINS = 1.0 / (24 * 60)

class BlatherNotifyMock < Minitest::Mock
	def initialize
		super
		@exes = []
	end

	def expect_execution(server, node, *args)
		exe = Execution.new(args)
		expect(
			:command_execution,
			exe,
			[server, node]
		)
		@exes << exe
	end

	def verify
		super
		@exes.each(&:verify)
	end

	class Execution < Minitest::Mock
		def initialize(args)
			super()
			args.each_slice(2) do |(submission, result)|
				expect(
					:fetch_and_submit,
					EMPromise.resolve(to_response(result)),
					**submission
				)
			end
		end

		using FormToH

		def to_response(form)
			OpenStruct.new(form.to_h)
		end
	end
end

def info(tel)
	CustomerInfo.new(
		plan_info: PlanInfo::NoPlan.new,
		tel: tel,
		balance: 0.to_d,
		cnam: nil
	)
end

def admin_info(customer_id, tel)
	AdminInfo.new(
		jid: Blather::JID.new("#{customer_id}@example.com"),
		customer_id: customer_id,
		fwd: nil,
		info: info(tel),
		call_info: "",
		trust_level: "",
		backend: BackendSgx.new(
			jid: Blather::JID.new("testroute"),
			from_jid: Blather::JID.new("customer_#{customer_id}@example.com"),
			creds: {},
			transcription_enabled: false,
			registered?: false,
			fwd: nil,
			ogm_url: nil
		)
	)
end

def menu
	FormTemplate.render("admin_menu")
end

class PortingStepTest < Minitest::Test
	Port = Struct.new(
		:order_id,
		:processing_status,
		:actual_foc_date,
		:last_modified_date,
		:customer_order_id,
		:billing_telephone_number
	)

	def test_ignore_submitted_ports
		redis = Minitest::Mock.new
		redis.expect(:exists, EMPromise.resolve(0), ["jmp_port_freeze-01"])

		step = PortingStepRepo.new(redis: redis).find(Port.new(
			"01",
			"SUBMITTED",
			nil,
			DateTime.now - 1 * MINS,
			"ignored",
			"9998887777"
		)).sync

		assert_kind_of PortingStepRepo::Wait, step
	end
	em :test_ignore_submitted_ports

	def test_ignore_recent_foc
		redis = Minitest::Mock.new
		redis.expect(:exists, EMPromise.resolve(0), ["jmp_port_freeze-01"])

		step = PortingStepRepo.new(redis: redis).find(Port.new(
			"01",
			"FOC",
			DateTime.now - 5 * MINS,
			DateTime.now - 1 * MINS,
			"ignored",
			"9998887777"
		)).sync

		assert_kind_of PortingStepRepo::Wait, step
	end
	em :test_ignore_recent_foc

	def test_warn_for_late_foc
		redis = Minitest::Mock.new
		redis.expect(:exists, EMPromise.resolve(0), ["jmp_port_freeze-01"])

		step = PortingStepRepo.new(redis: redis).find(Port.new(
			"01",
			"FOC",
			DateTime.now - 25 * MINS,
			DateTime.now - 1 * MINS,
			"ignored",
			"9998887777"
		)).sync

		assert_kind_of PortingStepRepo::Alert, step
		assert_equal :late_foc, step.key
		assert_kind_of PortingStepRepo::Wait, step.real_step
	end
	em :test_warn_for_late_foc

	def test_already_complete
		redis = Minitest::Mock.new
		redis.expect(:exists, EMPromise.resolve(0), ["jmp_port_freeze-01"])
		redis.expect(:exists, 1, ["jmp_port_complete-01"])

		step = PortingStepRepo.new(redis: redis).find(Port.new(
			"01",
			"COMPLETE",
			DateTime.now - 25 * MINS,
			DateTime.now - 1 * MINS,
			"completed",
			"9998887777"
		)).sync

		assert_kind_of PortingStepRepo::Done, step
		assert_mock redis
	end
	em :test_already_complete

	def test_change_number
		redis = Minitest::Mock.new
		redis.expect(:exists, EMPromise.resolve(0), ["jmp_port_freeze-01"])
		redis.expect(:exists, "0", ["jmp_port_complete-01"])

		notify = BlatherNotifyMock.new
		notify.expect_execution(
			"sgx", "customer info",
			{ q: "starting" }, admin_info("starting", "+19998881111").form
		)

		step = PortingStepRepo.new(
			redis: redis,
			blather_notify: notify,
			admin_server: "sgx"
		).find(Port.new(
			"01",
			"COMPLETE",
			DateTime.now - 25 * MINS,
			DateTime.now - 1 * MINS,
			"starting",
			"9998887777"
		)).sync

		assert_kind_of PortingStepRepo::Complete::AdminCommand::WrongNumber, step
		assert_mock redis
		assert_mock notify
	end
	em :test_change_number

	def test_first_reachability
		redis = Minitest::Mock.new
		redis.expect(:exists, EMPromise.resolve(0), ["jmp_port_freeze-01"])
		redis.expect(:exists, "0", ["jmp_port_complete-01"])

		notify = BlatherNotifyMock.new
		notify.expect_execution(
			"sgx", "customer info",
			{ q: "starting" }, admin_info("starting", "+19998887777").form
		)

		notify.expect_execution(
			"sgx", "reachability",
			{ tel: "9998887777", type: "voice" },
			FormTemplate.render("reachability_result", count: 0)
		)

		step = PortingStepRepo.new(
			redis: redis,
			blather_notify: notify,
			admin_server: "sgx"
		).find(Port.new(
			"01",
			"COMPLETE",
			DateTime.now - 25 * MINS,
			DateTime.now - 1 * MINS,
			"starting",
			"9998887777"
		)).sync

		assert_kind_of(
			PortingStepRepo::Complete::AdminCommand::GoodNumber::
				Reachability::RunTest,
			step
		)
		assert_equal "voice", step.type
		assert_mock redis
		assert_mock notify
	end
	em :test_first_reachability

	def test_reach_sms_reachability
		redis = Minitest::Mock.new
		redis.expect(:exists, EMPromise.resolve(0), ["jmp_port_freeze-01"])
		redis.expect(:exists, "0", ["jmp_port_complete-01"])

		notify = BlatherNotifyMock.new
		notify.expect_execution(
			"sgx", "customer info",
			{ q: "starting" }, admin_info("starting", "+19998887777").form
		)

		notify.expect_execution(
			"sgx", "reachability",
			{ tel: "9998887777", type: "voice" },
			FormTemplate.render("reachability_result", count: 1)
		)

		notify.expect_execution(
			"sgx", "reachability",
			{ tel: "9998887777", type: "sms" },
			FormTemplate.render("reachability_result", count: 0)
		)

		step = PortingStepRepo.new(
			redis: redis,
			blather_notify: notify,
			admin_server: "sgx"
		).find(Port.new(
			"01",
			"COMPLETE",
			DateTime.now - 25 * MINS,
			DateTime.now - 1 * MINS,
			"starting",
			"9998887777"
		)).sync

		assert_kind_of(
			PortingStepRepo::Complete::AdminCommand::GoodNumber::
				Reachability::RunTest,
			step
		)
		assert_equal "sms", step.type
		assert_mock redis
		assert_mock notify
	end
	em :test_reach_sms_reachability

	def test_all_reachable
		redis = Minitest::Mock.new
		redis.expect(:exists, EMPromise.resolve(0), ["jmp_port_freeze-01"])
		redis.expect(:exists, "0", ["jmp_port_complete-01"])

		notify = BlatherNotifyMock.new
		notify.expect_execution(
			"sgx", "customer info",
			{ q: "starting" }, admin_info("starting", "+19998887777").form
		)

		notify.expect_execution(
			"sgx", "reachability",
			{ tel: "9998887777", type: "voice" },
			FormTemplate.render("reachability_result", count: 1)
		)

		notify.expect_execution(
			"sgx", "reachability",
			{ tel: "9998887777", type: "sms" },
			FormTemplate.render("reachability_result", count: 1)
		)

		step = PortingStepRepo.new(
			redis: redis,
			blather_notify: notify,
			admin_server: "sgx"
		).find(Port.new(
			"01",
			"COMPLETE",
			DateTime.now - 25 * MINS,
			DateTime.now - 1 * MINS,
			"starting",
			"9998887777"
		)).sync

		assert_kind_of(
			PortingStepRepo::Complete::AdminCommand::GoodNumber::FinishUp,
			step
		)
		assert_mock redis
		assert_mock notify
	end
	em :test_all_reachable

	def test_not_done_in_time
		redis = Minitest::Mock.new
		redis.expect(:exists, EMPromise.resolve(0), ["jmp_port_freeze-01"])
		redis.expect(:exists, "0", ["jmp_port_complete-01"])

		notify = BlatherNotifyMock.new
		notify.expect_execution(
			"sgx", "customer info",
			{ q: "starting" }, admin_info("starting", "+19998887777").form
		)

		notify.expect_execution(
			"sgx", "reachability",
			{ tel: "9998887777", type: "voice" },
			FormTemplate.render("reachability_result", count: 0)
		)

		step = PortingStepRepo.new(
			redis: redis,
			blather_notify: notify,
			admin_server: "sgx"
		).find(Port.new(
			"01",
			"COMPLETE",
			DateTime.now - 55 * MINS,
			DateTime.now - 50 * MINS,
			"starting",
			"9998887777"
		)).sync

		assert_kind_of PortingStepRepo::Alert, step
		assert_equal :late_finish, step.key
		assert_kind_of(
			PortingStepRepo::Complete::AdminCommand::GoodNumber::
				Reachability::RunTest,
			step.real_step
		)
		assert_mock redis
		assert_mock notify
	end
	em :test_not_done_in_time

	def test_ignore_frozen_ports
		# This tests that we ignore ports in various states
		[
			Port.new(
				"01",
				"SUBMITTED",
				nil,
				DateTime.now - 1 * MINS,
				"ignored",
				"9998887777"
			),
			Port.new(
				"01",
				"FOC",
				DateTime.now - 300 * MINS,
				DateTime.now - 300 * MINS,
				"ignored",
				"9998887777"
			),
			Port.new(
				"01",
				"COMPLETED",
				DateTime.now - 10 * MINS,
				DateTime.now - 10 * MINS,
				"ignored",
				"9998887777"
			),
			Port.new(
				"01",
				"COMPLETED",
				DateTime.now - 300 * MINS,
				DateTime.now - 300 * MINS,
				"ignored",
				"9998887777"
			)
		].each do |port|
			redis = Minitest::Mock.new
			redis.expect(:exists, EMPromise.resolve(1), ["jmp_port_freeze-01"])

			step = PortingStepRepo.new(redis: redis).find(port).sync
			assert_kind_of PortingStepRepo::Frozen, step
		end
	end
	em :test_ignore_frozen_ports
end
