class GestaoDeEstoque::ItemDoRecebimentoDeMaterial < ApplicationRecord
	has_paper_trail

	attr_accessor :enviando_ao_almoxarifado_pelo_patrimonio, :item_selecionado

	belongs_to :item, class_name: "Base::Item"
	belongs_to :item_original, class_name: "Base::Item", foreign_key: :item_original_id
	belongs_to :unidade_de_medida_de_conversao, class_name: "UnidadeDeMedida", foreign_key: :unidade_de_medida_de_conversao_id
	belongs_to :recebimento_de_material, class_name: "GestaoDeEstoque::RecebimentoDeMaterial"

	has_many :movimentacoes_do_estoque, as: :origem
	has_many :informacoes_extras

	delegate :ordem_de_compra, to: :recebimento_de_material
	delegate :descricao_codigo_na_prefeitura_e_unidade_destacada, to: :item

	# validates_presence_of :item_id, :quantidade, :total, :valor_unitario, unless: Proc.new { recebimento_de_material.doacao? }
	validates_presence_of :item_id, :quantidade, :total, unless: Proc.new { recebimento_de_material.doacao? }
	validates_presence_of :quantidade_de_conversao, :valor_unitario_de_conversao, if: Proc.new { possui_conversao_de_unidade_de_medida? }
	validates_presence_of :unidade_de_medida_de_conversao_id, if: Proc.new { quantidade_de_conversao.present? }

	# validate :valida_quantidade_maior_que_saldo_disponivel, if: :possui_ordem_de_compra?
	validate :valida_quantidade_informacoes_extras, if: Proc.new { |item| item.informacoes_extras.any? }

	# before_validation :calcular_valor_unitario_de_conversao
	# before_validation :calcular_valor_total
	before_save :altera_item_da_nota_mista
	before_save :lancar_ocorrencia

	after_save :remover_este_item_se_quantidade_igual_a_zero, if: Proc.new {Rails.env.test? == false}
	accepts_nested_attributes_for :informacoes_extras, allow_destroy: true, reject_if: :all_blank

	def adiciona_ou_atualiza_saldo_no_estoque
		estoque = GestaoDeEstoque::Estoque.find_by(
			item_id: self.item_id,
			unidade_de_medida_id: retorna_unidade_de_medida_ou_conversao.id,
			almoxarifado_id: self.recebimento_de_material.almoxarifado_id,
			unidade_orcamentaria_id: self.recebimento_de_material.unidade_orcamentaria_id,
			sub_elemento_de_despesa_id: self.recebimento_de_material.sub_elemento_de_despesa_id,
			classificacao_do_detalhamento_id: self.recebimento_de_material.classificacao_do_detalhamento_id,
			orcamento_id: self.recebimento_de_material.orcamento_id
		)

		begin
			if estoque.present? && estoque.persisted?
				estoque.update_columns(
					saldo_da_ordem_de_compra: saldo_do_estoque
				)
				adiciona_movimentacao_no_estoque(estoque)
				busca_ou_cria_sub_estoque_por_validade(estoque)
			else
				cria_estoque(false)
			end
		rescue Exception => e
			raise e
		end
	end

	def adiciona_movimentacao_no_estoque(estoque)
		begin

			if estoque.present? && estoque.persisted?
				GestaoDeEstoque::MovimentacaoDoEstoque.create!(
					estoque_id: estoque.id,
					quantidade_entrada: retorna_quantidade_ou_quantidade_de_conversao,
					origem_id: recebimento_de_material.id,
					origem_type: recebimento_de_material.class.name,
					orcamento_id: self.recebimento_de_material.orcamento.id,
					unidade_orcamentaria_id: self.recebimento_de_material.unidade_orcamentaria.id,
					almoxarifado_id: self.recebimento_de_material.almoxarifado.id,
					valor_unitario: retorna_valor_unitario_ou_valor_unitario_de_conversao,
					valor_total: self.total
				)
			end
		rescue Exception => e
			raise e
		end
	end

	def eh_avulso?
		self.recebimento_de_material.avulso? rescue false
	end

	def possui_classificacao?
		self.recebimento_de_material.classificacao.present? rescue false
	end

	def possui_saldo_disponivel?
		item_estoque = GestaoDeEstoque::Estoque.find_by(item_id: self.item_id, unidade_de_medida_id: retorna_unidade_de_medida_ou_conversao.id, almoxarifado_id: self.recebimento_de_material.almoxarifado_id, unidade_orcamentaria_id: self.recebimento_de_material.unidade_orcamentaria_id, sub_elemento_de_despesa_id: self.recebimento_de_material.sub_elemento_de_despesa_id)
		unless self.possui_classificacao? || self.possui_ordem_de_compra?
			item_estoque.try(:quantidade_total_saldo_avulso).to_f > 0
		else
			item_estoque.try(:quantidade_total_saldo).to_f > 0
		end
	end

	def saldo_do_estoque
		if self.recebimento_de_material.almoxarifado_de_destino_id.present?
			saldo_do_estoque = GestaoDeEstoque::Estoque.find_by(item_id: self.item_id, unidade_de_medida_id: retorna_unidade_de_medida_ou_conversao.id, almoxarifado_id: self.recebimento_de_material.almoxarifado_de_destino_id, unidade_orcamentaria_id: self.recebimento_de_material.unidade_orcamentaria_de_destino_id, sub_elemento_de_despesa_id: self.recebimento_de_material.sub_elemento_de_despesa_id).quantidade_total_saldo rescue 0
		else
			saldo_do_estoque = GestaoDeEstoque::Estoque.find_by(item_id: self.item_id, unidade_de_medida_id: retorna_unidade_de_medida_ou_conversao.id, almoxarifado_id: self.recebimento_de_material.almoxarifado_id, unidade_orcamentaria_id: self.recebimento_de_material.unidade_orcamentaria_id, sub_elemento_de_despesa_id: self.recebimento_de_material.sub_elemento_de_despesa_id).quantidade_total_saldo rescue 0
		end
		(saldo_do_estoque + retorna_quantidade_ou_quantidade_de_conversao).to_f rescue 0
	end

	def possui_conversao_de_unidade_de_medida?
		self.unidade_de_medida_de_conversao.present?
	end

	def codigo_e_descricao_com_unidade_de_medida_especifica
		"#{self.try(:item).try(:codigo_e_descricao)} - #{self.try(:recebimento_de_material).try(:numero_da_ordem_de_compra)} (#{self.try(:possui_conversao_de_unidade_de_medida?) ? self.try(:unidade_de_medida_de_conversao).try(:descricao) : self.try(:item).try(:unidade_de_medida).try(:descricao)})"
	end

	def codigo_e_descricao_com_unidade_de_medida_especifica_destacada
		"#{self.try(:item).try(:codigo_e_descricao)} <strong>(#{self.try(:possui_conversao_de_unidade_de_medida?) ? self.try(:unidade_de_medida_de_conversao).try(:descricao) : self.try(:item).try(:unidade_de_medida).try(:descricao)})</strong>".html_safe
	end

	def retorna_unidade_de_medida_ou_conversao
		self.possui_conversao_de_unidade_de_medida? ? self.try(:unidade_de_medida_de_conversao) : self.try(:item).try(:unidade_de_medida) rescue false
	end

	def retorna_quantidade_ou_quantidade_de_conversao
		self.possui_conversao_de_unidade_de_medida? ? self.quantidade_de_conversao.to_f : self.quantidade.to_f
	end

	def retorna_valor_unitario_ou_valor_unitario_de_conversao
		self.possui_conversao_de_unidade_de_medida? ? self.valor_unitario_de_conversao : self.valor_unitario.to_f
	end

	def calcular_valor_unitario_de_conversao
		if self.quantidade_de_conversao.to_i > 0
			self.valor_unitario_de_conversao = (self.total.to_f / self.quantidade_de_conversao.to_f)
		end
	end

	def calcular_valor_total
		self.total = self.quantidade.to_d * self.valor_unitario.to_d
	end

	def unidade_de_medida_descricao
		if self.possui_conversao_de_unidade_de_medida?
			self.unidade_de_medida_de_conversao.descricao
		else
			self.item.unidade_de_medida.descricao
		end
	end

	def possui_ordem_de_compra?
		ordem_de_compra.present? rescue false
	end

	def quantidade_total_disponivel_da_ordem_de_compra
		self.ordem_de_compra.itens_da_ordem_de_compra.find_by(item_id: self.item.id).try(:quantidade_a_receber).to_f rescue 0
	end

	def quantidade_total_do_item_na_ordem_de_compra
		self.ordem_de_compra.itens_da_ordem_de_compra.find_by(item_id: self.item.id).try(:quantidade).to_f rescue 0
	end

	def valor_total_disponivel_da_ordem_de_compra
		self.ordem_de_compra.itens_da_ordem_de_compra.find_by(item_id: self.item.id).try(:total).to_f rescue 0
	end

	# def valor_total_de_conversao
	# 	(self.valor_unitario_de_conversao.to_f * self.quantidade_de_conversao.to_f).to_f
	# end

	def saldo_inicial_da_ordem_de_compra
		if quantidade_total_disponivel_da_ordem_de_compra.present?
			quantidade_total_disponivel_da_ordem_de_compra.to_f - self.quantidade.to_f
		else
			0
		end
	end

	def saldo_atual_da_ordem_de_compra
		saldo_ordem_de_compra = GestaoDeEstoque::Estoque.find_by(item_id: self.item_id, unidade_de_medida_id: retorna_unidade_de_medida_ou_conversao.id, sub_elemento_de_despesa_id: self.recebimento_de_material.sub_elemento_de_despesa_id).saldo_da_ordem_de_compra.to_f rescue 0
		if saldo_ordem_de_compra.to_f <= 0
			0
		else
			saldo_ordem_de_compra.to_f
		end
	end

	def saldo_atual_em_valor_da_ordem_de_compra
		saldo_ordem_de_compra = GestaoDeEstoque::Estoque.find_by(item_id: self.item_id, unidade_de_medida_id: retorna_unidade_de_medida_ou_conversao.id, sub_elemento_de_despesa_id: self.recebimento_de_material.sub_elemento_de_despesa_id).saldo_da_ordem_de_compra.to_f rescue 0
		if saldo_ordem_de_compra.to_f <= 0
			0
		else
			(saldo_atual_da_ordem_de_compra * self.valor_unitario).to_f
		end
	end

	def saldo_atual_do_recebimento_de_material
		GestaoDeEstoque::Estoque.find_by(item_id: self.item_id, unidade_de_medida_id: retorna_unidade_de_medida_ou_conversao.id, almoxarifado_id: self.recebimento_de_material.almoxarifado_id, unidade_orcamentaria_id: self.recebimento_de_material.unidade_orcamentaria_id, sub_elemento_de_despesa_id: self.recebimento_de_material.sub_elemento_de_despesa_id).quantidade_total_saldo rescue 0
	end

	def descricao
		"#{self.item.descricao}"
	end

	def codigo_e_descricao
		"#{self.item.id} - #{self.item.descricao}"
	end

	def valida_quantidade_maior_que_saldo_disponivel
		if self.possui_ordem_de_compra? && self.new_record? && (self.recebimento_de_material.recebimento_total? || self.recebimento_de_material.recebimento_parcial?)
			errors.add(:quantidade, "A quantidade informada é maior que saldo disponível") if quantidade_total_disponivel_da_ordem_de_compra.to_f < self.quantidade.to_f
		end
	end

	def remover_este_item_se_quantidade_igual_a_zero
		self.delete if self.quantidade.to_f == 0
	end

	def cria_estoque(eh_destino)
		unidade_orcamentaria = eh_destino == true ? self.recebimento_de_material.unidade_orcamentaria_de_destino_id : self.recebimento_de_material.unidade_orcamentaria_id
		almoxarifado = eh_destino == true ? self.recebimento_de_material.almoxarifado_de_destino_id : self.recebimento_de_material.almoxarifado_id
		return false unless unidade_orcamentaria.present? || almoxarifado.present?

		estoque = GestaoDeEstoque::Estoque.create!(
			item_id: self.item_id,
			unidade_de_medida_id: retorna_unidade_de_medida_ou_conversao.id,
			saldo_da_ordem_de_compra: retorna_quantidade_ou_quantidade_de_conversao,
			unidade_orcamentaria_id: unidade_orcamentaria,
			almoxarifado_id:  almoxarifado,
			tipo_de_material: self.recebimento_de_material.tipo_de_material,
			sub_elemento_de_despesa_id: self.recebimento_de_material.sub_elemento_de_despesa_id,
			classificacao_do_detalhamento_id: self.recebimento_de_material.classificacao_do_detalhamento_id,
			orcamento_id: self.recebimento_de_material.orcamento_id
		)

		adiciona_movimentacao_no_estoque(estoque)
		busca_ou_cria_sub_estoque_por_validade(estoque)
	end

	def corrige_estoque_duplicados
		estoques = GestaoDeEstoque::Estoque.joins(:almoxarifado).where('gestao_de_estoque_estoques.unidade_orcamentaria_id = ?
			and gestao_de_estoque_estoques.item_id = ? and gestao_de_estoque_estoques.unidade_de_medida_id = ? and gestao_de_estoque_estoques.almoxarifado_id = ?
			and gestao_de_estoque_estoques.orcamento_id = ?', self.recebimento_de_material.unidade_orcamentaria_id, self.item_id,
			retorna_unidade_de_medida_ou_conversao.id, self.recebimento_de_material.almoxarifado_id, self.recebimento_de_material.orcamento_id)
			.order(created_at: :asc)

		estoque_original = estoques.first
		estoques_ha_deletar = estoques.select{ |e| e.id != estoque_original.id }
		estoques_ha_deletar.each do |e|
			e.movimentacoes_do_estoque.each do |m|
				m.update_column(:estoque_id, estoque_original.id)
			end
		end

		saldo_para_adicionar = (estoque_original.saldo_da_ordem_de_compra + estoques_ha_deletar.sum(&:saldo_da_ordem_de_compra))
		estoque_original.update_columns(saldo_da_ordem_de_compra: saldo_para_adicionar, sub_elemento_de_despesa_id: estoques_ha_deletar.first.sub_elemento_de_despesa_id) if estoques_ha_deletar.present?
		estoques_ha_deletar.each do |e|
			e.delete
		end
	end

	def valida_quantidade_informacoes_extras
		if self.informacoes_extras.map{ |info| info.quantidade.to_f }.sum != self.quantidade
			errors.add(:base, "A soma das quantidades deve ser igual a #{self.quantidade}")
		end
	end

	def busca_ou_cria_sub_estoque_por_validade(estoque)
		self.informacoes_extras.each do |informacao_extra|
			sub_estoque = estoque.sub_estoques_por_validade.find_by( validade: informacao_extra.validade )
			if sub_estoque.nil?
				sub_estoque = estoque.sub_estoques_por_validade.new(validade: informacao_extra.validade, quantidade: 0)
			end
			sub_estoque.quantidade += informacao_extra.quantidade
			sub_estoque.save!
		end
	end

	def altera_item_da_nota_mista
		if self.item_selecionado.present? 
			if self.item_selecionado != self.item_id
				self.item_original_id = self.item_id if self.item_original_id_was.nil? && self.item_original_id.to_i == 0
				self.item_original_id = self.item_original_id_was if self.item_original_id_was.present? && self.item_original_id.to_i == 0				
				self.item_id = self.item_selecionado
			else
				self.item_original_id = nil if self.item_original_id_was.nil? && self.item_original_id.to_i == 0
				self.item_original_id = self.item_original_id_was if self.item_original_id_was.present? && self.item_original_id.to_i == 0
			end
		end
	end

	def lancar_ocorrencia
		if self.item_id_changed? && self.item_original.present?
			GestaoDeEstoque::OcorrenciaDoRecebimentoDeMaterial.create(
				recebimento_de_material_id: self.recebimento_de_material_id,
				tipo_de_ocorrencia: :alteracao_de_item,
				observacao: "O Item #{self.item_original.codigo_e_descricao[0..100]}... foi alterado para o Item #{self.item.codigo_e_descricao[0..100]}..."
			)
		end
	end
end
