# frozen_string_literal: true

require "lazy_object"
require "ruby-bandwidth-iris"
require_relative "geo_code_repo"
require_relative "local_calling_guide_repo"

class BandwidthTnRepo
	class RateCenter
		# @return [String, nil]
		attr_reader :region

		# @return [String, nil]
		attr_reader :locality

		# @return [String, nil]
		attr_reader :lcg_region

		# @return [String, nil]
		attr_reader :lcg_locality

		# @return [Array(String, String)]
		def to_a
			[region, locality]
		end

		# @param btn [BandwidthIris::Tn]
		# @param lcg_repo [LocalCallingGuideRepo]
		# @param geo_code_repo [GeoCodeRepo]
		# @return [EMPromise<RateCenter>]
		def self.for(
			btn,
			lcg_repo: LazyObject.new { LocalCallingGuideRepo.new },
			geo_code_repo: LazyObject.new { GeoCodeRepo.new }
		)
			[
				Source::Bandwidth.new(btn),
				Source::Geocode.new(btn, lcg_repo, geo_code_repo),
				Source::LCG.new(btn, lcg_repo)
			].reduce(EMPromise.resolve(new)) { |promise, source|
				promise.then { |acc|
					next acc if acc.settled?

					source.fetch(acc).then(&acc.method(:fold))
				}
			}
		end

		# @return [Boolean]
		def settled?
			return false unless readable(@region)
			return false unless readable(@locality)

			true
		end

	protected

		# @param value [String, nil]
		# @return [String, nil]
		def readable(value)
			value if value&.match?(/\d/) == false
		end

		# @param kwargs [Hash]
		# @return [self]
		def fold(kwargs)
			kwargs.each_pair do |k, v|
				instance_variable_set(
					:"@#{k}",
					readable(instance_variable_get(:"@#{k}")) || v
				)
			end
			self
		end

		class Source
			# @param acc [RateCenter]
			# @return [EMPromise<Hash>]
			def fetch(acc)
				raise NotImplementedError
			end

			class Bandwidth < self
				# @param btn [BandwidthIris::Tn]
				def initialize(btn)
					@btn = btn
				end

				# @param _acc [RateCenter]
				# @return [EMPromise<Hash>]
				def fetch(_acc)
					city, state = @btn.get_details.values_at(
						:city, :state
					)
					EMPromise.resolve(
						{ locality: city, region: state }
					)
				rescue BandwidthIris::Errors::GenericError
					EMPromise.resolve({})
				end
			end

			class LCG < self
				# @param btn [BandwidthIris::Tn]
				# @param lcg_repo [LocalCallingGuideRepo]
				def initialize(btn, lcg_repo)
					@btn = btn
					@lcg_repo = lcg_repo
				end

				# @param acc [RateCenter]
				# @return [EMPromise<Hash>]
				def fetch(acc)
					EMPromise.resolve(
						{
							locality: acc.lcg_locality,
							region: acc.lcg_region
						}
					)
				end
			end

			class Geocode < self
				# @param btn [BandwidthIris::Tn]
				# @param lcg_repo [LocalCallingGuideRepo]
				# @param geo_code_repo [GeoCodeRepo]
				def initialize(btn, lcg_repo, geo_code_repo)
					@btn = btn
					@geo_code_repo = geo_code_repo
					@lcg_repo = lcg_repo
				end

				# @param _acc [RateCenter]
				# @return [EMPromise<Hash>]
				def fetch(_acc)
					npa, nxx, * = @btn.telephone_number.scan(/.{1,3}/).to_a
					@lcg_repo.find(npa, nxx).then { |lcg|
						cache = lcg.to_h.transform_keys(&"lcg_".method(:+))
						@geo_code_repo.reverse(lcg.lat, lcg.lon).then { |gc|
							gc.to_h.slice(:locality, :region).merge(cache)
						}.catch { cache }
					}
				end
			end
		end
	end

	STASH_QUERY = <<~SQL
		INSERT INTO tel_inventory (
			tel,
			region,
			locality,
			source,
			available_after,
			premium_price
		) VALUES (
			$1,
			$2,
			$3,
			$4,
			LOCALTIMESTAMP + '1 MONTH',
			$5
		)
	SQL

	# @param local_calling_guide_repo [LocalCallingGuideRepo]
	# @param geo_code_repo [GeoCodeRepo]
	def initialize(
		local_calling_guide_repo: LazyObject.new { LocalCallingGuideRepo.new },
		geo_code_repo: LazyObject.new { GeoCodeRepo.new }
	)
		@move_client =
			BandwidthIris::Client.new(
				account_id: CONFIG[:keep_area_codes_in][:account],
				client_id: CONFIG[:creds][:client_id],
				client_secret: CONFIG[:creds][:client_secret]
			)
		@local_calling_guide_repo = local_calling_guide_repo
		@geo_code_repo = geo_code_repo
	end

	def find(tel)
		BandwidthIris::Tn.new(telephone_number: tel).get_details
	rescue StandardError
		{}
	end

	def put_lidb_name(tel, lidb_name)
		BandwidthIris::Lidb.create(
			lidb_tn_groups: { lidb_tn_group: {
				telephone_numbers: { telephone_number: tel.sub(/\A\+1/, "") },
				subscriber_information: lidb_name,
				use_type: "RESIDENTIAL", visibility: "PUBLIC"
			} }
		)
	rescue BandwidthIris::Errors::GenericError
		raise "Could not set CNAM, please contact support"
	end

	# @param btn [BandwidthIris::Tn]
	# @param premium_price [Numeric]
	# @return [EMPromise]
	def stash_for_later(btn, premium_price)
		tel = "+1#{btn.telephone_number}"
		LOG.info "stash_for_later #{tel}\n#{caller}"
		RateCenter.for(
			btn,
			lcg_repo: @local_calling_guide_repo,
			geo_code_repo: @geo_code_repo
		).then { |loc|
			DB.exec(STASH_QUERY, [tel, *loc, CONFIG[:creds][:account], premium_price])
		}
	end

	def disconnect(tel, order_name)
		tn = tel.sub(/\A\+1/, "")
		code_and_price = CONFIG[:keep_area_codes].find { |k|
			tn.start_with?(k[:area_code])
		}
		unless code_and_price
			return BandwidthIris::Disconnect.create(order_name, tn)
		end

		btn = BandwidthIris::Tn.new({ telephone_number: tn }, @move_client)
		stash_for_later(btn, code_and_price[:premium_price] || 0)
	end

	def move(tel, order_name, source_account_id)
		tn = tel.sub(/\A\+1/, "")
		BandwidthIris::Tn.new({ telephone_number: tn }, @move_client).move(
			customer_order_id: order_name,
			source_account_id: source_account_id,
			site_id: CONFIG[:bandwidth_site],
			sip_peer_id: CONFIG[:bandwidth_peer]
		)
	end
end
