1# frozen_string_literal: true
  2
  3require "value_semantics/monkey_patched"
  4
  5require_relative "tts_template"
  6require_relative "low_balance"
  7
  8class CallAttempt
  9	def self.for(customer:, usage:, **kwargs)
 10		credit = [(customer.minute_limit&.to_d || 100) - usage, 0].max +
 11		         customer.balance
 12		@kinds.each do |kind|
 13			ca = kind.call(
 14				customer: customer, usage: usage, credit: credit,
 15				**kwargs.merge(limits(customer, usage, credit, **kwargs))
 16			)
 17			return ca if ca
 18		end
 19
 20		raise "No CallAttempt matched"
 21	end
 22
 23	def self.limits(customer, usage, credit, rate:, **)
 24		unless customer&.minute_limit && usage && rate && rate.positive?
 25			return { limit_remaining: 100, max_minutes: 100 }
 26		end
 27
 28		can_use = customer.minute_limit.to_d + customer.monthly_overage_limit
 29		{
 30			limit_remaining: ([can_use - usage, 0].max / rate).to_i,
 31			max_minutes: (credit / rate).to_i
 32		}
 33	end
 34
 35	def self.register(&maybe_mk)
 36		@kinds ||= []
 37		@kinds << maybe_mk
 38	end
 39
 40	value_semantics do
 41		customer_id String
 42		from String
 43		to(/\A\+\d+\Z/)
 44		sgx Blather::JID
 45		call_id String
 46		direction Either(:inbound, :outbound)
 47		limit_remaining Integer
 48		max_minutes Integer
 49	end
 50
 51	def to_render
 52		["#{direction}/connect", { locals: to_h }]
 53	end
 54
 55	def to_s
 56		"Allowed(max_minutes: #{max_minutes}, limit_remaining: #{limit_remaining})"
 57	end
 58
 59	def create_call(fwd, *args, &block)
 60		fwd.create_call(*args, &block)
 61	end
 62
 63	def as_json(*)
 64		to_h
 65	end
 66
 67	def to_json(*args)
 68		as_json.to_json(*args)
 69	end
 70
 71	class Expired
 72		CallAttempt.register do |customer:, direction:, **|
 73			new(direction: direction) if customer.plan_name && !customer.active?
 74		end
 75
 76		value_semantics do
 77			direction Either(:inbound, :outbound)
 78		end
 79
 80		def view
 81			"#{direction}/expired"
 82		end
 83
 84		def tts
 85			TTSTemplate.new(view).tts(self)
 86		end
 87
 88		def to_render
 89			[view]
 90		end
 91
 92		def to_s
 93			"Expired"
 94		end
 95
 96		def create_call(*); end
 97
 98		def as_json(*)
 99			tts.empty? ? {} : { tts: tts }
100		end
101
102		def to_json(*args)
103			as_json.to_json(*args)
104		end
105	end
106
107	class Unsupported
108		CallAttempt.register do |supported:, direction:, **|
109			new(direction: direction) unless supported
110		end
111
112		value_semantics do
113			direction Either(:inbound, :outbound)
114		end
115
116		def view
117			"#{direction}/unsupported"
118		end
119
120		def tts
121			TTSTemplate.new(view).tts(self)
122		end
123
124		def to_render
125			[view]
126		end
127
128		def to_s
129			"Unsupported"
130		end
131
132		def create_call(*); end
133
134		def as_json(*)
135			tts.empty? ? {} : { tts: tts }
136		end
137
138		def to_json(*args)
139			as_json.to_json(*args)
140		end
141	end
142
143	class NoBalance
144		CallAttempt.register do |credit:, rate:, **kwargs|
145			self.for(rate: rate, **kwargs) if credit < rate * 10
146		end
147
148		def self.for(customer:, direction:, low_balance: LowBalance, **kwargs)
149			low_balance.for(customer).then(&:notify!).then do |amount|
150				if amount&.positive?
151					CallAttempt.for(
152						customer: customer.with_balance(customer.balance + amount),
153						**kwargs.merge(direction: direction)
154					)
155				else
156					NoBalance.new(balance: customer.balance, direction: direction)
157				end
158			end
159		end
160
161		value_semantics do
162			balance Numeric
163			direction Either(:inbound, :outbound)
164		end
165
166		def view
167			"#{direction}/no_balance"
168		end
169
170		def tts
171			TTSTemplate.new(view).tts(self)
172		end
173
174		def to_render
175			[view, { locals: to_h }]
176		end
177
178		def to_s
179			"NoBalance"
180		end
181
182		def create_call(*); end
183
184		def as_json(*)
185			tts.empty? ? {} : { tts: tts }
186		end
187
188		def to_json(*args)
189			as_json.to_json(*args)
190		end
191	end
192
193	class AtLimit
194		value_semantics do
195			customer_id String
196			from String
197			to(/\A\+\d+\Z/)
198			call_id String
199			direction Either(:inbound, :outbound)
200			limit_remaining Integer
201			max_minutes Integer
202		end
203
204		CallAttempt.register do |digits: nil, limit_remaining:, customer:, **kwargs|
205			if digits != "1" && limit_remaining < 10
206				new(
207					**kwargs
208						.merge(
209							limit_remaining: limit_remaining,
210							customer_id: customer.customer_id
211						).slice(*value_semantics.attributes.map(&:name))
212				)
213			end
214		end
215
216		def view
217			"#{direction}/at_limit"
218		end
219
220		def tts
221			TTSTemplate.new(view).tts(self)
222		end
223
224		def to_render
225			[view, { locals: to_h }]
226		end
227
228		def to_s
229			"AtLimit(max_minutes: #{max_minutes}, "\
230			"limit_remaining: #{limit_remaining})"
231		end
232
233		def create_call(fwd, *args, &block)
234			fwd.create_call(*args, &block)
235		end
236
237		def as_json(*)
238			{
239				tts: tts,
240				from: from,
241				to: to,
242				customer_id: customer_id,
243				limit_remaining: limit_remaining,
244				max_minutes: max_minutes
245			}
246		end
247
248		def to_json(*args)
249			as_json.to_json(*args)
250		end
251	end
252
253	register do |customer:, **kwargs|
254		new(
255			**kwargs
256				.merge(customer_id: customer.customer_id, sgx: customer.sgx)
257				.slice(*value_semantics.attributes.map(&:name))
258		)
259	end
260
261	class TollFree
262		CallAttempt.register do |rate:, customer:, **kwargs|
263			if rate&.zero?
264				new(
265					**kwargs
266						.merge(customer_id: customer.customer_id, sgx: customer.sgx)
267						.slice(*value_semantics.attributes.map(&:name))
268				)
269			end
270		end
271
272		value_semantics do
273			customer_id String
274			from String
275			to(/\A\+\d+\Z/)
276			sgx Blather::JID
277			call_id String
278			direction Either(:inbound, :outbound)
279		end
280
281		def to_render
282			["#{direction}/connect", { locals: to_h }]
283		end
284
285		def to_s
286			"TollFree"
287		end
288
289		def create_call(fwd, *args, &block)
290			fwd.create_call(*args, &block)
291		end
292
293		def as_json(*)
294			to_h
295		end
296
297		def to_json(*args)
298			as_json.to_json(*args)
299		end
300	end
301end