# frozen_string_literal: true

require "value_semantics/monkey_patched"
require_relative "../admin_action"
require_relative "../form_to_h"
require_relative "../trust_level_repo"

class AdminAction
	class SetTrustLevel < AdminAction
		include Isomorphic

		class Command
			using FormToH

			def self.for(target_customer, reply:)
				TrustLevelRepo.new.find_manual(target_customer.customer_id).then { |man|
					new(
						man,
						customer_id: target_customer.customer_id
					)
				}.then { |x|
					reply.call(x.form).then(&x.method(:create))
				}
			end

			def initialize(manual, **bag)
				@manual = manual
				@bag = bag.compact
			end

			def form
				FormTemplate.render(
					"admin_set_trust_level",
					manual: @manual,
					levels: TrustLevel.constants.map(&:to_s).reject { |x| x == "Manual" }
				)
			end

			def create(result)
				AdminAction::SetTrustLevel.for(
					previous_trust_level: @manual,
					**@bag,
					**result.form.to_h
						.reject { |_k, v| v == "automatic" }.transform_keys(&:to_sym)
				)
			end
		end

		InvalidLevel = Struct.new(:level, :levels) {
			def to_s
				"Trust level invalid: expected #{levels.join(', ')}, got #{level}"
			end
		}

		NoMatch = Struct.new(:expected, :actual) {
			def to_s
				"Trust level doesn't match: expected #{expected}, got #{actual}"
			end
		}

		def initialize(previous_trust_level: nil, new_trust_level: nil, **kwargs)
			super(
				previous_trust_level: previous_trust_level.presence,
				new_trust_level: new_trust_level.presence,
				**kwargs
			)
		end

		def customer_id
			@attributes[:customer_id]
		end

		def previous_trust_level
			@attributes[:previous_trust_level]
		end

		def new_trust_level
			@attributes[:new_trust_level]
		end

		# If I don't check previous_trust_level here I could get into this
		# situation:
		# 1. Set from automatic to Customer
		# 2. Undo
		# 3. Set from automatic to Paragon
		# 4. Undo the undo (redo set from automatic to customer)
		# Now if I don't check previous_trust_level we'll enqueue a thing that says
		# we've set from manual to customer, but that's not actually what we did! We
		# set from Paragon to customer. If I undo that now I won't end up back a
		# paragon, I'll end up at automatic again, which isn't the state I was in a
		# second ago
		def check_forward
			EMPromise.all([
				check_noop,
				check_valid,
				check_consistent
			])
		end

		def forward
			TrustLevelRepo.new.put(customer_id, new_trust_level).then { self }
		end

		def to_reverse
			with(
				previous_trust_level: new_trust_level,
				new_trust_level: previous_trust_level
			)
		end

		def to_s
			"set_trust_level(#{customer_id}): "\
			"#{pretty(previous_trust_level)} -> #{pretty(new_trust_level)}"
		end

	protected

		def check_noop
			EMPromise.reject(NoOp.new) if new_trust_level == previous_trust_level
		end

		def check_valid
			options = TrustLevel.constants.map(&:to_s)
			return unless new_trust_level && !options.include?(new_trust_level)

			EMPromise.reject(InvalidLevel.new(new_trust_level, options))
		end

		def check_consistent
			TrustLevelRepo.new.find_manual(customer_id).then { |trust|
				unless previous_trust_level == trust
					EMPromise.reject(
						NoMatch.new(pretty(previous_trust_level), pretty(trust))
					)
				end
			}
		end

		def pretty(level)
			level || "automatic"
		end
	end
end
