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 create_call(*); end
95
96 def as_json(*)
97 { tts: tts }
98 end
99
100 def to_json(*args)
101 as_json.to_json(*args)
102 end
103 end
104
105 class Unsupported
106 CallAttempt.register do |supported:, direction:, **|
107 new(direction: direction) unless supported
108 end
109
110 value_semantics do
111 direction Either(:inbound, :outbound)
112 end
113
114 def view
115 "#{direction}/unsupported"
116 end
117
118 def tts
119 TTSTemplate.new(view).tts(self)
120 end
121
122 def to_render
123 [view]
124 end
125
126 def to_s
127 "Unsupported"
128 end
129
130 def create_call(*); end
131
132 def as_json(*)
133 { tts: tts }
134 end
135
136 def to_json(*args)
137 as_json.to_json(*args)
138 end
139 end
140
141 class NoBalance
142 CallAttempt.register do |credit:, rate:, **kwargs|
143 self.for(rate: rate, **kwargs) if credit < rate * 10
144 end
145
146 def self.for(customer:, direction:, low_balance: LowBalance, **kwargs)
147 low_balance.for(customer).then(&:notify!).then do |amount|
148 if amount&.positive?
149 CallAttempt.for(
150 customer: customer.with_balance(customer.balance + amount),
151 **kwargs.merge(direction: direction)
152 )
153 else
154 NoBalance.new(balance: customer.balance, direction: direction)
155 end
156 end
157 end
158
159 value_semantics do
160 balance Numeric
161 direction Either(:inbound, :outbound)
162 end
163
164 def view
165 "#{direction}/no_balance"
166 end
167
168 def tts
169 TTSTemplate.new(view).tts(self)
170 end
171
172 def to_render
173 [view, { locals: to_h }]
174 end
175
176 def to_s
177 "NoBalance"
178 end
179
180 def create_call(*); end
181
182 def as_json(*)
183 { tts: tts }
184 end
185
186 def to_json(*args)
187 as_json.to_json(*args)
188 end
189 end
190
191 class AtLimit
192 value_semantics do
193 customer_id String
194 from String
195 to(/\A\+\d+\Z/)
196 call_id String
197 direction Either(:inbound, :outbound)
198 limit_remaining Integer
199 max_minutes Integer
200 end
201
202 CallAttempt.register do |digits: nil, limit_remaining:, customer:, **kwargs|
203 if digits != "1" && limit_remaining < 10
204 new(
205 **kwargs
206 .merge(
207 limit_remaining: limit_remaining,
208 customer_id: customer.customer_id
209 ).slice(*value_semantics.attributes.map(&:name))
210 )
211 end
212 end
213
214 def view
215 "#{direction}/at_limit"
216 end
217
218 def tts
219 TTSTemplate.new(view).tts(self)
220 end
221
222 def to_render
223 [view, { locals: to_h }]
224 end
225
226 def to_s
227 "AtLimit(max_minutes: #{max_minutes}, "\
228 "limit_remaining: #{limit_remaining})"
229 end
230
231 def create_call(fwd, *args, &block)
232 fwd.create_call(*args, &block)
233 end
234
235 def as_json(*)
236 {
237 tts: tts,
238 from: from,
239 to: to,
240 customer_id: customer_id,
241 limit_remaining: limit_remaining,
242 max_minutes: max_minutes
243 }
244 end
245
246 def to_json(*args)
247 as_json.to_json(*args)
248 end
249 end
250
251 register do |customer:, **kwargs|
252 new(
253 **kwargs
254 .merge(customer_id: customer.customer_id)
255 .slice(*value_semantics.attributes.map(&:name))
256 )
257 end
258end