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