# frozen_string_literal: true

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

class AdminAction
	class NumberChange < AdminAction
		include Isomorphic
		class Command
			using FormToH

			def self.for(target_customer, reply:)
				unless (registration = target_customer.registered?)
					return EMPromise.reject("Customer not registered")
				end

				EMPromise.resolve(
					new(
						customer_id: target_customer.customer_id,
						old_backend: target_customer.sgx.strip!.to_s,
						old_tel: registration.phone
					)
				).then { |x| reply.call(x.form).then(&x.method(:change)) }
			end

			def initialize(**bag)
				@bag = bag
			end

			def form
				FormTemplate.render("admin_number_change")
			end

			def change(result)
				AdminAction::NumberChange.for(
					**@bag,
					**result.form.to_h
						.reject { |_k, v| v == "nil" || v.to_s.empty? }
						.tap { |form_h|
							form_h["new_backend"] ||= @bag[:old_backend]
							form_h["new_tel"] ||= @bag[:old_tel]
						}
						.transform_keys(&:to_sym)
				)
			end
		end

		NilKey = Struct.new(:key) {
			def to_s
				"Expected a key with a value, but #{key} has no value."
			end
		}

		UnknownBackend = Struct.new(:backend, :expected) {
			def to_s
				"Got unknown backend: #{backend}, expected one of #{expected}"
			end
		}

		def customer_id
			@attributes[:customer_id]
		end

		def old_tel
			@attributes[:old_tel]
		end

		def new_tel
			@attributes[:new_tel]
		end

		def old_backend
			@attributes[:old_backend]
		end

		def new_backend
			@attributes[:new_backend]
		end

		def should_delete?
			["1", "true"].include?(@attributes[:should_delete])
		end

		def check_forward
			EMPromise.all([check_noop, check_cat_jid, check_backend])
		end

		def forward
			EMPromise.all([
				new_tel != old_tel && change_catapult_fwd!,
				TrivialBackendSgxRepo.new(
					redis: REDIS
				).put(customer_id, new_backend, new_tel),
				should_disconnect_old_number? && disconnect_number
			]).then { self }
		end

		def change_catapult_fwd!
			REDIS.rename("catapult_fwd-#{old_tel}", "catapult_fwd-#{new_tel}")
		end

		def to_reverse
			with(
				old_tel: new_tel,
				new_tel: old_tel,
				old_backend: new_backend,
				new_backend: old_backend
			)
		end

		def to_s
			base = "Change Number\n"
			base += "	[move backend?]: #{old_backend} -> #{new_backend}\n"
			base += "	[change number?]: #{old_tel} -> #{new_tel}"
			base + delete_warning
		end

	protected

		def disconnect_number
			# Order name is limited to 40 characters
			# Assuming 12 chars for new_tel and 12 for customer_id, this is tight
			# but ok
			BandwidthTnRepo.new.disconnect(
				old_tel,
				"cust #{customer_id} swap to #{new_tel}"
			)
		end

		def delete_warning
			return "" unless should_delete? && !first_time?

			" * NOT DELETING"
		end

		def check_noop
			if new_tel == old_tel && new_backend == old_backend
				EMPromise.reject(NoOp.new)
			else
				EMPromise.resolve(nil)
			end
		end

		def check_cat_jid
			cat_jid = "catapult_jid-#{old_tel}"
			REDIS.exists(cat_jid).then { |v|
				EMPromise.reject(NilKey.new(cat_jid)) unless v == 1
			}
		end

		def check_backend
			unless (expected = CONFIG[:sgx_creds].keys.map(
				&:to_s
			).push(CONFIG[:sgx])).include?(new_backend)
				EMPromise.reject(UnknownBackend.new(new_backend, expected))
			end
		end

		def should_disconnect_old_number?
			should_delete? &&
				first_time? &&
				old_tel != new_tel &&
				old_backend == CONFIG[:sgx]
		end
	end
end
