1# frozen_string_literal: true
  2
  3require "bigdecimal/util"
  4
  5require_relative "low_balance"
  6require_relative "transaction"
  7
  8class SIMOrder
  9	def self.for(customer, price:, **kwargs)
 10		price = price.to_i / 100.to_d
 11		return new(customer, price: price, **kwargs) if customer.balance >= price
 12
 13		LowBalance::AutoTopUp.for(customer, price).then do |top_up|
 14			if top_up.can_top_up?
 15				WithTopUp.new(customer, self, price: price, top_up: top_up, **kwargs)
 16			else
 17				PleaseTopUp.new(price: price, **kwargs)
 18			end
 19		end
 20	end
 21
 22	def self.label
 23		"SIM"
 24	end
 25
 26	def self.fillable_fields
 27		[
 28			{
 29				type: "text-multi",
 30				label: "Shipping Address",
 31				var: "addr",
 32				required: true
 33			},
 34			{
 35				type: "text-single",
 36				var: "nickname",
 37				label: "Nickname",
 38				required: false
 39			}
 40		]
 41	end
 42
 43	def initialize(customer, price:, plan:)
 44		@customer = customer
 45		@price = price
 46		@plan = plan
 47		@sim_repo = SIMRepo.new(db: DB)
 48	end
 49
 50	def form
 51		FormTemplate.render(
 52			"order_sim/with_balance",
 53			price: @price,
 54			plan: @plan,
 55			label: self.class.label,
 56			fillable_fields: self.class.fillable_fields
 57		)
 58	end
 59
 60	def complete(iq)
 61		form = iq.form
 62		EMPromise.resolve(nil).then {
 63			commit(form.field("nickname")&.value.presence || self.class.label)
 64		}.then do |sim|
 65			Ack.new(
 66				@customer,
 67				sim,
 68				Array(form.field("addr").value).join("\n")
 69			).complete
 70		end
 71	end
 72
 73	class Ack
 74		def initialize(customer, sim, addr)
 75			@customer = customer
 76			@sim = sim
 77			@addr = addr
 78		end
 79
 80		def complete
 81			@customer.stanza_from(Blather::Stanza::Message.new(
 82				Blather::JID.new(""), # Doesn't matter, sgx is set to direct target
 83				"SIM ORDER: #{@sim.iccid}\n#{@addr}"
 84			))
 85			Command.finish(
 86				"You will receive an notice from support when your SIM ships."
 87			)
 88		end
 89	end
 90
 91protected
 92
 93	# @param [String, nil] nickname the nickname, if any, assigned to
 94	# 					   the sim by the customer
 95	def commit(nickname)
 96		DB.transaction do
 97			sim = @sim_repo.available.sync
 98			@sim_repo.put_owner(sim, @customer, nickname)
 99			keepgo_tx = @sim_repo.refill(sim, amount_mb: 1024).sync
100			raise "SIM activation failed" unless keepgo_tx["ack"] == "success"
101
102			transaction(sim, keepgo_tx).insert_tx
103			sim
104		end
105	end
106
107	def transaction(sim, keepgo_tx)
108		Transaction.new(
109			customer_id: @customer.customer_id,
110			transaction_id: keepgo_tx["transaction_id"],
111			amount: -@price,
112			note: "#{self.class.label} Activation #{sim.iccid}"
113		)
114	end
115
116	class ESIM < SIMOrder
117		def self.label
118			"eSIM"
119		end
120
121		def self.fillable_fields
122			[
123				{
124					type: "text-single",
125					var: "nickname",
126					label: "Nickname",
127					required: false
128				}
129			]
130		end
131
132		# @param [Blather::Stanza::Iq] iq the stanza
133		# 		  containing a filled out `order_sim/with_balance`
134		def complete(iq)
135			EMPromise.resolve(nil).then {
136				commit(
137					iq.form.field("nickname")&.value.presence || self.class.label
138				)
139			}.then do |sim|
140				ActivationCode.new(sim).complete
141			end
142		end
143	end
144
145	class ActivationCode
146		# @param [Sim] sim the sim which the customer
147		# 			   just ordered
148		def initialize(sim)
149			@sim = sim
150		end
151
152		def complete
153			Command.finish do |reply|
154				oob = OOB.find_or_create(reply.command)
155				oob.url = @sim.lpa_code
156				oob.desc = "LPA Activation Code"
157				reply.command << FormTemplate.render(
158					"order_sim/esim_complete", sim: @sim
159				)
160			end
161		end
162	end
163
164	class WithTopUp
165		def initialize(customer, continue, price:, plan:, top_up:)
166			@customer = customer
167			@price = price
168			@plan = plan
169			@top_up = top_up
170			@continue = continue
171		end
172
173		def form
174			FormTemplate.render(
175				"order_sim/with_top_up",
176				price: @price,
177				plan: @plan,
178				top_up_amount: @top_up.top_up_amount,
179				label: @continue.label,
180				fillable_fields: @continue.fillable_fields
181			)
182		end
183
184		def complete(iq)
185			@top_up.notify!.then do |amount|
186				if amount.positive?
187					@continue.new(@customer, price: @price, plan: @plan).complete(iq)
188				else
189					Command.finish("Could not top up", type: :error)
190				end
191			end
192		end
193	end
194
195	class PleaseTopUp
196		def initialize(price:, plan:)
197			@price = price
198			@plan = plan
199		end
200
201		def form
202			FormTemplate.render(
203				"order_sim/please_top_up",
204				price: @price,
205				plan: @plan
206			)
207		end
208
209		def complete(_)
210			Command.finish
211		end
212	end
213end