# frozen_string_literal: true

require "bigdecimal/util"

require_relative "low_balance"
require_relative "transaction"

class SIMOrder
	def self.for(customer, price:, **kwargs)
		price = price.to_i / 100.to_d
		return new(customer, price: price, **kwargs) if customer.balance >= price

		LowBalance::AutoTopUp.for(customer, price).then do |top_up|
			if top_up.can_top_up?
				WithTopUp.new(customer, self, price: price, top_up: top_up, **kwargs)
			else
				PleaseTopUp.new(price: price, **kwargs)
			end
		end
	end

	def self.label
		"SIM"
	end

	def self.fillable_fields
		[
			{
				type: "text-multi",
				label: "Shipping Address",
				var: "addr",
				required: true
			},
			{
				type: "text-single",
				var: "nickname",
				label: "Nickname",
				required: false
			}
		]
	end

	def initialize(customer, price:, plan:)
		@customer = customer
		@price = price
		@plan = plan
		@sim_repo = SIMRepo.new(db: DB)
	end

	def form
		FormTemplate.render(
			"order_sim/with_balance",
			price: @price,
			plan: @plan,
			label: self.class.label,
			fillable_fields: self.class.fillable_fields
		)
	end

	def complete(iq)
		form = iq.form
		EMPromise.resolve(nil).then {
			commit(form.field("nickname").value.presence || self.class.label)
		}.then do |sim|
			Ack.new(
				@customer,
				sim,
				Array(form.field("addr").value).join("\n")
			).complete
		end
	end

	class Ack
		def initialize(customer, sim, addr)
			@customer = customer
			@sim = sim
			@addr = addr
		end

		def complete
			@customer.stanza_from(Blather::Stanza::Message.new(
				Blather::JID.new(""), # Doesn't matter, sgx is set to direct target
				"SIM ORDER: #{@sim.iccid}\n#{@addr}"
			))
			Command.finish(
				"You will receive an notice from support when your SIM ships."
			)
		end
	end

protected

	# @param [String, nil] nickname the nickname, if any, assigned to
	# 					   the sim by the customer
	def commit(nickname)
		DB.transaction do
			sim = @sim_repo.available.sync
			@sim_repo.put_owner(sim, @customer, nickname)
			keepgo_tx = @sim_repo.refill(sim, amount_mb: 1024).sync
			raise "SIM activation failed" unless keepgo_tx["ack"] == "success"

			transaction(sim, keepgo_tx).insert_tx
			sim
		end
	end

	def transaction(sim, keepgo_tx)
		Transaction.new(
			customer_id: @customer.customer_id,
			transaction_id: keepgo_tx["transaction_id"],
			amount: -@price,
			note: "#{self.class.label} Activation #{sim.iccid}"
		)
	end

	class ESIM < SIMOrder
		def self.label
			"eSIM"
		end

		def self.fillable_fields
			[]
		end

		# @param [Blather::Stanza::Iq] iq the stanza
		# 		  containing a filled out `order_sim/with_balance`
		def complete(iq)
			EMPromise.resolve(nil).then {
				commit(
					nickname: iq.form.field("nickname").value.presence || self.class.label
				)
			}.then do |sim|
				ActivationCode.new(sim).complete
			end
		end
	end

	class ActivationCode
		# @param [Sim] sim the sim which the customer
		# 			   just ordered
		def initialize(sim)
			@sim = sim
		end

		def complete
			Command.finish do |reply|
				oob = OOB.find_or_create(reply.command)
				oob.url = @sim.lpa_code
				oob.desc = "LPA Activation Code"
				reply.command << FormTemplate.render(
					"order_sim/esim_complete", sim: @sim
				)
			end
		end
	end

	class WithTopUp
		def initialize(customer, continue, price:, plan:, top_up:)
			@customer = customer
			@price = price
			@plan = plan
			@top_up = top_up
			@continue = continue
		end

		def form
			FormTemplate.render(
				"order_sim/with_top_up",
				price: @price,
				plan: @plan,
				top_up_amount: @top_up.top_up_amount,
				label: @continue.label,
				fillable_fields: @continue.fillable_fields
			)
		end

		def complete(iq)
			@top_up.notify!.then do |amount|
				if amount.positive?
					@continue.new(@customer, price: @price, plan: @plan).complete(iq)
				else
					Command.finish("Could not top up", type: :error)
				end
			end
		end
	end

	class PleaseTopUp
		def initialize(price:, plan:)
			@price = price
			@plan = plan
		end

		def form
			FormTemplate.render(
				"order_sim/please_top_up",
				price: @price,
				plan: @plan
			)
		end

		def complete(_)
			Command.finish
		end
	end
end
