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