# frozen_string_literal: true

require "ruby-bandwidth-iris"
Faraday.default_adapter = :em_synchrony

require_relative "form_template"

class TelSelections
	THIRTY_DAYS = 60 * 60 * 24 * 30

	def initialize(redis: REDIS)
		@redis = redis
	end

	def set(jid, tel)
		@redis.setex("pending_tel_for-#{jid}", THIRTY_DAYS, tel)
	end

	def delete(jid)
		@redis.del("pending_tel_for-#{jid}")
	end

	def [](jid)
		@redis.get("pending_tel_for-#{jid}").then do |tel|
			tel ? HaveTel.new(tel) : ChooseTel.new
		end
	end

	class HaveTel
		def initialize(tel)
			@tel = tel
		end

		def choose_tel
			EMPromise.resolve(@tel)
		end
	end

	class ChooseTel
		def choose_tel(error: nil)
			Command.reply { |reply|
				reply.allowed_actions = [:next]
				reply.command << FormTemplate.render("tn_search", error: error)
			}.then do |iq|
				choose_from_list(AvailableNumber.for(iq.form).tns)
			rescue StandardError
				choose_tel(error: $!.to_s)
			end
		end

		def choose_from_list(tns)
			if tns.empty?
				choose_tel(error: "No numbers found, try another search.")
			else
				Command.reply { |reply|
					reply.allowed_actions = [:next]
					reply.command << FormTemplate.render("tn_list", tns: tns)
				}.then { |iq| iq.form.field("tel").value.to_s.strip }
			end
		end

		class AvailableNumber
			def self.for(form)
				new(
					Q
					.for(form.field("q").value.to_s.strip).iris_query
					.merge(enableTNDetail: true, LCA: false)
					.merge(Quantity.for(form).iris_query)
				)
			end

			def initialize(iris_query)
				@iris_query = iris_query
			end

			def tns
				Command.log.debug("BandwidthIris::AvailableNumber.list", @iris_query)
				BandwidthIris::AvailableNumber.list(@iris_query).map(&Tn.method(:new))
			end

			class Quantity
				def self.for(form)
					rsm_max = form.find(
						"ns:set/ns:max",
						ns: "http://jabber.org/protocol/rsm"
					).first
					if rsm_max
						new(rsm_max.content.to_i)
					else
						Default.new
					end
				end

				def initialize(quantity)
					@quantity = quantity
				end

				def iris_query
					{ quantity: @quantity }
				end

				# NOTE: Gajim sends back the whole list on submit, so big
				# lists can cause issues
				class Default
					def iris_query
						{ quantity: 10 }
					end
				end
			end
		end

		class Tn
			attr_reader :tel

			def initialize(full_number:, city:, state:, **)
				@tel = "+1#{full_number}"
				@locality = city
				@region = state
			end

			def formatted_tel
				@tel =~ /\A\+1(\d{3})(\d{3})(\d+)\Z/
				"(#{$1}) #{$2}-#{$3}"
			end

			def option
				op = Blather::Stanza::X::Field::Option.new(value: tel, label: to_s)
				op << reference
				op
			end

			def reference
				Nokogiri::XML::Builder.new { |xml|
					xml.reference(
						xmlns: "urn:xmpp:reference:0",
						begin: 0,
						end: formatted_tel.length - 1,
						type: "data",
						uri: "tel:#{tel}"
					)
				}.doc.root
			end

			def to_s
				"#{formatted_tel} (#{@locality}, #{@region})"
			end
		end

		class Q
			def self.register(regex, &block)
				@queries ||= []
				@queries << [regex, block]
			end

			def self.for(q)
				@queries.each do |(regex, block)|
					match_data = (q =~ regex)
					return block.call($1 || $&, *$~.to_a[2..-1]) if match_data
				end

				raise "Format not recognized: #{q}"
			end

			def initialize(q)
				@q = q
			end

			{
				areaCode: [:AreaCode, /\A[2-9][0-9]{2}\Z/],
				npaNxx: [:NpaNxx, /\A(?:[2-9][0-9]{2}){2}\Z/],
				npaNxxx: [:NpaNxxx, /\A(?:[2-9][0-9]{2}){2}[0-9]\Z/],
				zip: [:PostalCode, /\A\d{5}(?:-\d{4})?\Z/],
				localVanity: [:LocalVanity, /\A~(.+)\Z/]
			}.each do |k, args|
				klass = const_set(
					args[0],
					Class.new(Q) {
						define_method(:iris_query) do
							{ k => @q }
						end
					}
				)

				args[1..-1].each do |regex|
					register(regex) { |q| klass.new(q) }
				end
			end

			class CityState
				Q.register(/\A([^,]+)\s*,\s*([a-zA-Z]{2})\Z/, &method(:new))

				CITY_MAP = {
					"ajax" => "Ajax-Pickering",
					"kitchener" => "Kitchener-Waterloo",
					"new york" => "New York City",
					"pickering" => "Ajax-Pickering",
					"sault ste marie" => "sault sainte marie",
					"sault ste. marie" => "sault sainte marie",
					"south durham" => "Durham",
					"township of langley" => "Langley",
					"waterloo" => "Kitchener-Waterloo",
					"west durham" => "Durham"
				}.freeze

				STATE_MAP = {
					"QC" => "PQ"
				}.freeze

				def initialize(city, state)
					@city = CITY_MAP.fetch(city.downcase, city)
					@state = STATE_MAP.fetch(state.upcase, state.upcase)
				end

				def iris_query
					{ city: @city, state: @state }
				end
			end
		end
	end
end
