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 create_call(fwd, *args, &block)
52 fwd.create_call(*args, &block)
53 end
54
55 def as_json(*)
56 {
57 from: from,
58 to: to,
59 customer_id: customer_id,
60 limit_remaining: limit_remaining,
61 max_minutes: max_minutes
62 }
63 end
64
65 def to_json(*args)
66 as_json.to_json(*args)
67 end
68
69 class Expired
70 CallAttempt.register do |customer:, direction:, **|
71 new(direction: direction) if customer.plan_name && !customer.active?
72 end
73
74 value_semantics do
75 direction Either(:inbound, :outbound)
76 end
77
78 def view
79 "#{direction}/expired"
80 end
81
82 def tts
83 TTSTemplate.new(view).tts(self)
84 end
85
86 def to_render
87 [view]
88 end
89
90 def create_call(*); end
91
92 def as_json(*)
93 { tts: tts }
94 end
95
96 def to_json(*args)
97 as_json.to_json(*args)
98 end
99 end
100
101 class Unsupported
102 CallAttempt.register do |supported:, direction:, **|
103 new(direction: direction) unless supported
104 end
105
106 value_semantics do
107 direction Either(:inbound, :outbound)
108 end
109
110 def view
111 "#{direction}/unsupported"
112 end
113
114 def tts
115 TTSTemplate.new(view).tts(self)
116 end
117
118 def to_render
119 [view]
120 end
121
122 def create_call(*); end
123
124 def as_json(*)
125 { tts: tts }
126 end
127
128 def to_json(*args)
129 as_json.to_json(*args)
130 end
131 end
132
133 class NoBalance
134 CallAttempt.register do |credit:, rate:, **kwargs|
135 self.for(rate: rate, **kwargs) if credit < rate * 10
136 end
137
138 def self.for(customer:, direction:, **kwargs)
139 LowBalance.for(customer).then(&:notify!).then do |amount|
140 if amount&.positive?
141 CallAttempt.for(
142 customer: customer.with_balance(customer.balance + amount),
143 **kwargs.merge(direction: direction)
144 )
145 else
146 NoBalance.new(balance: customer.balance, direction: direction)
147 end
148 end
149 end
150
151 value_semantics do
152 balance Numeric
153 direction Either(:inbound, :outbound)
154 end
155
156 def view
157 "#{direction}/no_balance"
158 end
159
160 def tts
161 TTSTemplate.new(view).tts(self)
162 end
163
164 def to_render
165 [view, { locals: to_h }]
166 end
167
168 def create_call(*); end
169
170 def as_json(*)
171 { tts: tts }
172 end
173
174 def to_json(*args)
175 as_json.to_json(*args)
176 end
177 end
178
179 class AtLimit
180 value_semantics do
181 customer_id String
182 from String
183 to(/\A\+\d+\Z/)
184 call_id String
185 direction Either(:inbound, :outbound)
186 limit_remaining Integer
187 max_minutes Integer
188 end
189
190 CallAttempt.register do |digits: nil, limit_remaining:, customer:, **kwargs|
191 if digits != "1" && limit_remaining < 10
192 new(
193 **kwargs
194 .merge(
195 limit_remaining: limit_remaining,
196 customer_id: customer.customer_id
197 ).slice(*value_semantics.attributes.map(&:name))
198 )
199 end
200 end
201
202 def view
203 "#{direction}/at_limit"
204 end
205
206 def tts
207 TTSTemplate.new(view).tts(self)
208 end
209
210 def to_render
211 [view, { locals: to_h }]
212 end
213
214 def create_call(fwd, *args, &block)
215 fwd.create_call(*args, &block)
216 end
217
218 def as_json(*)
219 {
220 tts: tts,
221 from: from,
222 to: to,
223 customer_id: customer_id,
224 limit_remaining: limit_remaining,
225 max_minutes: max_minutes
226 }
227 end
228
229 def to_json(*args)
230 as_json.to_json(*args)
231 end
232 end
233
234 register do |customer:, **kwargs|
235 new(
236 **kwargs
237 .merge(customer_id: customer.customer_id)
238 .slice(*value_semantics.attributes.map(&:name))
239 )
240 end
241end