# frozen_string_literal: true

require "test_helper"

require "ostruct"

require_relative "../lib/sim_order"

class SIMOrderTest < Minitest::Test
	SIMOrder::DB = Minitest::Mock.new
	Transaction::DB = Minitest::Mock.new

	def setup
		@price = 5000
		@plan_name = "better than no plan"
		@sim_repo = Minitest::Mock.new
		@sim = SIM.new(
			iccid: "123",
			nickname: nil,
			lpa_code: "LPA:abc",
			remaining_usage_kb: 1025,
			remaining_days: 3,
			notes: "no notes"
		)
	end

	def test_for_enough_balance
		assert_kind_of(
			SIMOrder,
			SIMOrder.for(customer(balance: 60), price: @price, plan: @plan_name)
		)
	end

	def test_for_insufficient_balance_with_top_up
		customer = customer(balance: 40)

		LowBalance::AutoTopUp.stub(
			:for,
			OpenStruct.new(can_top_up?: true),
			[customer, @price]
		) do
			assert_kind_of(
				SIMOrder::WithTopUp,
				SIMOrder.for(customer, price: @price, plan: @plan_name)
			)
		end
	end

	def test_for_insufficient_balance_please_top_up
		customer = customer(balance: 40)

		LowBalance::AutoTopUp.stub(
			:for,
			OpenStruct.new(can_top_up?: false),
			[customer, @price]
		) do
			assert_kind_of(
				SIMOrder::PleaseTopUp,
				SIMOrder.for(customer, price: @price, plan: @plan_name)
			)
		end
	end

	def test_complete_nil_nick
		execute_command {
			customer = Minitest::Mock.new(customer("123", balance: 100))
			customer.expect(
				:stanza_from,
				EMPromise.resolve(nil),
				[Blather::Stanza::Message]
			)

			SIMOrder::DB.expect(
				:transaction,
				EMPromise.resolve(@sim)
			) do |&blk|
				blk.call
			end

			Transaction::DB.expect(
				:exec,
				nil,
				[
					String,
					Matching.new { |params|
						assert_equal "123", params[0]
						assert_equal "tx123", params[1]
						assert_kind_of Time, params[2]
						assert_kind_of Time, params[3]
						assert_in_delta(-(@price / 100).to_d, params[4], 0.05)
						assert_equal "SIM Activation 123", params[5]
					}
				]
			)

			@sim_repo.expect(:available, EMPromise.resolve(@sim), [])
			@sim_repo.expect(:put_owner, nil, [@sim, customer, "SIM"])
			@sim_repo.expect(
				:refill,
				EMPromise.resolve(
					OpenStruct.new(ack: "success", transaction_id: "tx123")
				)
			) do |refill_sim, amount_mb:|
				@sim == refill_sim && amount_mb == 1024
			end

			order_form = Blather::Stanza::Iq::Command.new.tap { |iq|
				iq.form.fields = [
					{ var: "addr", value: "123 Main St" },
					{ var: "nickname", value: nil }
				]
			}

			Command::COMMAND_MANAGER.expect(
				:write,
				EMPromise.reject(:test_result),
				[Matching.new do |reply|
					assert_equal :executing, reply.status
					assert_equal :info, reply.note_type
					assert_equal(
						"You will receive a notice from support when your SIM ships.",
						reply.note.content
					)
				end]
			)

			sim_order = SIMOrder.for(customer, price: @price, plan: @plan_name)
			sim_order.instance_variable_set(:@sim_repo, @sim_repo)

			assert_equal(
				:test_result,
				sim_order.complete(order_form).catch { |e| e }.sync
			)

			assert_mock Transaction::DB
			assert_mock SIMOrder::DB
			assert_mock customer
			assert_mock @sim_repo
		}
	end
	em :test_complete_nil_nick

	def test_complete_nick_present
		execute_command {
			Command::COMMAND_MANAGER.expect(
				:write,
				EMPromise.reject(:test_result),
				[Matching.new do |reply|
					assert_equal :info, reply.note_type
					assert_equal :executing, reply.status
					assert_equal(
						"You will receive a notice from support when your SIM ships.",
						reply.note.content
					)
				end]
			)

			customer = Minitest::Mock.new(customer("123", balance: 100))
			customer.expect(
				:stanza_from,
				EMPromise.resolve(nil),
				[Blather::Stanza::Message]
			)
			Transaction::DB.expect(
				:exec,
				nil,
				[
					String,
					Matching.new { |params|
						assert_equal "123", params[0]
						assert_equal "tx123", params[1]
						assert_kind_of Time, params[2]
						assert_kind_of Time, params[3]
						assert_in_delta(-(@price / 100).to_d, params[4], 0.05)
						assert_equal "SIM Activation 123", params[5]
					}
				]
			)

			SIMOrder::DB.expect(
				:transaction,
				@sim
			) do |&blk|
				blk.call
			end

			@sim_repo.expect(:available, EMPromise.resolve(@sim), [])
			@sim_repo.expect(
				:put_owner,
				nil,
				[@sim, customer, "test_nick"]
			)
			@sim_repo.expect(
				:refill,
				EMPromise.resolve(
					OpenStruct.new(ack: "success", transaction_id: "tx123")
				)
			) do |refill_sim, amount_mb:|
				@sim == refill_sim && amount_mb == 1024
			end

			order_form = Blather::Stanza::Iq::Command.new.tap { |iq|
				iq.form.fields = [
					{ var: "addr", value: "123 Main St" },
					{ var: "nickname", value: "test_nick" }
				]
			}

			sim_order =
				SIMOrder.for(customer, price: @price, plan: @plan_name)
			sim_order.instance_variable_set(:@sim_repo, @sim_repo)

			assert_equal(
				:test_result,
				sim_order.complete(order_form).catch { |e| e }.sync
			)

			assert_mock Transaction::DB
			assert_mock SIMOrder::DB
			assert_mock customer
			assert_mock @sim_repo
		}
	end
	em :test_complete_nick_present

	def test_please_top_up_process_with_insufficient_balanace
		customer = customer(balance: 0)

		execute_command {
			LowBalance::AutoTopUp.stub(
				:for,
				OpenStruct.new(can_top_up?: false),
				[customer, @price]
			) do
				order = SIMOrder.for(customer, price: @price, plan: @plan_name)

				assert_kind_of(
					SIMOrder::PleaseTopUp,
					order
				)

				Command::COMMAND_MANAGER.expect(
					:write,
					EMPromise.reject(:test_result),
					[Matching.new do |iq|
						assert iq.allowed_actions.include?(:complete)
					end]
				)

				order.process.catch { |e| e }.sync
			end
		}
	end
	em :test_please_top_up_process_with_insufficient_balanace
end
