1# frozen_string_literal: true
  2
  3require "value_semantics/monkey_patched"
  4require_relative "../admin_action"
  5require_relative "../form_to_h"
  6require_relative "../trust_level_repo"
  7
  8class AdminAction
  9	class SetTrustLevel < AdminAction
 10		include Isomorphic
 11
 12		class Command
 13			using FormToH
 14
 15			def self.for(target_customer, reply:)
 16				TrustLevelRepo.new.find_manual(target_customer.customer_id).then { |man|
 17					new(
 18						man,
 19						customer_id: target_customer.customer_id
 20					)
 21				}.then { |x|
 22					reply.call(x.form).then(&x.method(:create))
 23				}
 24			end
 25
 26			def initialize(manual, **bag)
 27				@manual = manual
 28				@bag = bag.compact
 29			end
 30
 31			def form
 32				FormTemplate.render(
 33					"admin_set_trust_level",
 34					manual: @manual,
 35					levels: TrustLevel.constants.map(&:to_s).reject { |x| x == "Manual" }
 36				)
 37			end
 38
 39			def create(result)
 40				AdminAction::SetTrustLevel.for(
 41					previous_trust_level: @manual,
 42					**@bag,
 43					**result.form.to_h
 44						.reject { |_k, v| v == "automatic" }.transform_keys(&:to_sym)
 45				)
 46			end
 47		end
 48
 49		InvalidLevel = Struct.new(:level, :levels) {
 50			def to_s
 51				"Trust level invalid: expected #{levels.join(', ')}, got #{level}"
 52			end
 53		}
 54
 55		NoMatch = Struct.new(:expected, :actual) {
 56			def to_s
 57				"Trust level doesn't match: expected #{expected}, got #{actual}"
 58			end
 59		}
 60
 61		def initialize(previous_trust_level: nil, new_trust_level: nil, **kwargs)
 62			super(
 63				previous_trust_level: previous_trust_level.presence,
 64				new_trust_level: new_trust_level.presence,
 65				**kwargs
 66			)
 67		end
 68
 69		def customer_id
 70			@attributes[:customer_id]
 71		end
 72
 73		def previous_trust_level
 74			@attributes[:previous_trust_level]
 75		end
 76
 77		def new_trust_level
 78			@attributes[:new_trust_level]
 79		end
 80
 81		# If I don't check previous_trust_level here I could get into this
 82		# situation:
 83		# 1. Set from automatic to Customer
 84		# 2. Undo
 85		# 3. Set from automatic to Paragon
 86		# 4. Undo the undo (redo set from automatic to customer)
 87		# Now if I don't check previous_trust_level we'll enqueue a thing that says
 88		# we've set from manual to customer, but that's not actually what we did! We
 89		# set from Paragon to customer. If I undo that now I won't end up back a
 90		# paragon, I'll end up at automatic again, which isn't the state I was in a
 91		# second ago
 92		def check_forward
 93			EMPromise.all([
 94				check_noop,
 95				check_valid,
 96				check_consistent
 97			])
 98		end
 99
100		def forward
101			TrustLevelRepo.new.put(customer_id, new_trust_level).then { self }
102		end
103
104		def to_reverse
105			with(
106				previous_trust_level: new_trust_level,
107				new_trust_level: previous_trust_level
108			)
109		end
110
111		def to_s
112			"set_trust_level(#{customer_id}): "\
113			"#{pretty(previous_trust_level)} -> #{pretty(new_trust_level)}"
114		end
115
116	protected
117
118		def check_noop
119			EMPromise.reject(NoOp.new) if new_trust_level == previous_trust_level
120		end
121
122		def check_valid
123			options = TrustLevel.constants.map(&:to_s)
124			return unless new_trust_level && !options.include?(new_trust_level)
125
126			EMPromise.reject(InvalidLevel.new(new_trust_level, options))
127		end
128
129		def check_consistent
130			TrustLevelRepo.new.find_manual(customer_id).then { |trust|
131				unless previous_trust_level == trust
132					EMPromise.reject(
133						NoMatch.new(pretty(previous_trust_level), pretty(trust))
134					)
135				end
136			}
137		end
138
139		def pretty(level)
140			level || "automatic"
141		end
142	end
143end