class Licitacao::OrcamentoDaDespesaPorProjeto < ApplicationRecord
	has_paper_trail

	attr_accessor :elemento_de_despesa_por_subacao_id, :subacao_id, :unidade_orcamentaria_id

	delegate :elemento_de_despesa_por_subacao, to: :orcamento_da_despesa, allow_nil: true
	delegate :subacao, to: :elemento_de_despesa_por_subacao, allow_nil: true
	delegate :unidade_orcamentaria, to: :subacao, allow_nil: true

	belongs_to :projeto
	belongs_to :orcamento_da_despesa, class_name: "Loa::OrcamentoDaDespesa"
	belongs_to :sub_elemento_de_despesa, class_name: "Contabilidade::SubElementoDeDespesa"

	has_many :itens_do_orcamento_da_despesa_por_projeto, class_name: "Licitacao::ItemDoOrcamentoDaDespesaPorProjeto", dependent: :destroy

	validates_presence_of :valor, :orcamento_da_despesa_id, :projeto_id, :sub_elemento_de_despesa_id
	validates_uniqueness_of :orcamento_da_despesa_id, scope: [:projeto_id, :sub_elemento_de_despesa_id]

	validate :nao_excede_o_valor_estimado_por_unidade_orcamentaria, if: Proc.new { self.unidade_orcamentaria_id.present? && ( self.projeto.try(:pedido).try(:projeto_simplificado?) == false || self.projeto.try(:pedido).try(:projeto_simplificado?).nil? )}
	validates :itens_do_orcamento_da_despesa_por_projeto, uniq_nested_attributes: { atributo: :item_do_lote_id, mensagem: "item deve ser único dentro de uma dotação" }

	accepts_nested_attributes_for :itens_do_orcamento_da_despesa_por_projeto, reject_if: :all_blank, allow_destroy: true

	after_save :atualiza_valor, unless: Proc.new { itens_do_orcamento_da_despesa_por_projeto.empty? }

	def classificacao_completa_com_subelemento
		"#{orcamento_da_despesa.classificacao_parcial}
		#{subacao.try(:acao).try(:codigo_completo)}
		#{subacao.try(:codigo) if orcamento_da_despesa.fonte_de_recursos.modulo.trabalha_com_subacao?}
		#{sub_elemento_de_despesa.codigo_formatado}
		#{orcamento_da_despesa.fonte_de_recursos.codigo_completo}"
	end

	def variante_da_classificacao_completa_com_subelemento
		"#{orcamento_da_despesa.classificacao_parcial}
		#{subacao.try(:acao).try(:codigo_completo)}
		#{subacao.try(:codigo) if orcamento_da_despesa.fonte_de_recursos.modulo.trabalha_com_subacao?}
		#{sub_elemento_de_despesa.variante_do_codigo_formatado}
		#{orcamento_da_despesa.fonte_de_recursos.codigo_completo}"
	end

	def funcao_programatica_do_sim
		orcamento_da_despesa.funcao_programatica_do_sim
	end

	def tem_saldo?
		self.valor <= self.orcamento_da_despesa.saldo
	end

	def valor_de_diferenca_do_saldo
		if Configuracao.last.comprometimento_de_dotacoes?
			(self.valor.to_f - self.orcamento_da_despesa.saldo_disponivel_para_novos_processos.to_f).abs
		else
			(self.valor.to_f - self.orcamento_da_despesa.saldo.to_f).abs
		end
	end

	# Utilizado ao parametrizar comprometer dotações
	def valor_comprometido_por_dotacao
		if projeto.present? && !projeto.fracassado? && !projeto.cancelado? && !projeto.desertado?
			valor_inicial_do_projeto = projeto.valor_estimado_global.to_f
			valor_atualizado_do_projeto = (projeto.homologado?) ? projeto.valor_total.to_f : valor_inicial_do_projeto
			proporcional_dotacao = (valor_inicial_do_projeto > 0) ? (self.valor / valor_inicial_do_projeto) : 0

			valor_comprometido = (valor_atualizado_do_projeto * proporcional_dotacao).to_f.round(2) - valor_empenhado_por_dotacao.to_f
			valor_comprometido < 0 ? 0 : valor_comprometido
		else
			0
		end
	end

	def valor_empenhado_por_dotacao
		projeto.empenhos.joins(:orcamento_da_despesa).where(loa_orcamentos_da_despesa: {id: self.orcamento_da_despesa_id}).sum(&:definir_valor_do_empenho)
	end

	def valor_anulado_por_dotacao
		projeto.empenhos.joins(:orcamento_da_despesa).where(loa_orcamentos_da_despesa: {id: self.orcamento_da_despesa_id}).sum(&:valor_anulado)
	end

	def saldo_por_dotacao
		self.valor - valor_empenhado_por_dotacao + valor_anulado_por_dotacao
	end

	def saldo_a_vincular_por_item_do_lote item_do_lote
		item_do_lote.itens_por_unidade(unidade_orcamentaria.id).to_f - projeto.itens_dos_orcamentos_da_despesa_por_projeto.joins(orcamento_da_despesa_por_projeto: [orcamento_da_despesa: [elemento_de_despesa_por_subacao: [subacao: :unidade_orcamentaria]]]).where(item_do_lote: item_do_lote).where(loa_unidades_orcamentarias: {id: unidade_orcamentaria.id}).sum(:quantidade).to_f
	end

	# TCM
	def to_sim valor_total
		begin
			valor_da_dotacao = valor_total ||= self.valor

			texto = ""
			texto << (projeto.parceria_osc? ? "536".to_s.sim_preenche(3) : "507".to_s.sim_preenche(3)) + ","
			texto << Configuracao.first.codigo_do_municipio_no_tcm.to_s.sim_preenche(3) + ","
			texto << projeto.data_de_autuacao.sim_data + ","
			texto << projeto.numero_do_processo.sim_limite(15) + ","
			if projeto.parceria_osc?
				texto << projeto.sigla_da_modalidade_do_processo.sim_preenche(1) + ","
			end
			texto << projeto.orcamento.exercicio.to_s + "00" + ","
			texto << orcamento_da_despesa.elemento_de_despesa_por_subacao.subacao.unidade_orcamentaria.orgao.codigo.to_s.sim_preenche(2) + ","
			texto << orcamento_da_despesa.elemento_de_despesa_por_subacao.subacao.unidade_orcamentaria.codigo.to_s.codigo_uo_to_sim + ","
			texto << orcamento_da_despesa.elemento_de_despesa_por_subacao.subacao.funcao.codigo.to_s.sim_preenche(2) + ","
			texto << orcamento_da_despesa.elemento_de_despesa_por_subacao.subacao.subfuncao.codigo.to_s.sim_preenche(3) + ","
			texto << orcamento_da_despesa.elemento_de_despesa_por_subacao.subacao.acao.programa_de_governo.codigo.to_s.sim_preenche(4) + ","
			texto << orcamento_da_despesa.elemento_de_despesa_por_subacao.subacao.acao.natureza_da_acao.codigo.to_s.sim_limite(1) + ","
			texto << orcamento_da_despesa.elemento_de_despesa_por_subacao.subacao.acao.codigo.to_s.sim_preenche(3) + ","
			texto << (projeto.orcamento.trabalha_com_subacao? ? orcamento_da_despesa.elemento_de_despesa_por_subacao.subacao.codigo.to_s.sim_preenche(4) : "0000".to_s.sim_preenche(4)) + "," 
			texto << orcamento_da_despesa.elemento_de_despesa_por_subacao.elemento_de_despesa.codigo.to_s.sim_limite(8) + ","
			texto << orcamento_da_despesa.fonte_de_recursos.codigo_completo.to_s.sim_limite(1) + ","
			texto << orcamento_da_despesa.fonte_de_recursos.codigo_sim + ","
			texto << format("%.2f", valor_da_dotacao.round(2)) + ","
			return texto
		rescue => e
			if e.class.to_s == "NoMethodError"
				atributo_falho = e.message.split(" ")[2]
				coluna = CSV.parse(texto, :headers => false).last.count
				raise e.mensagem_traduzida(self, "", atributo_falho, coluna)
			else
				raise e
			end
		end
	end

	def itens_do_pedido
		@itens_do_pedido ||= Licitacao::ItemDoPedido.where(id: itens_do_pedido_por_unidade_orcamentaria.pluck(:item_do_pedido_id))
	end

	def itens_do_pedido_por_unidade_orcamentaria
		unidade_orcamentaria_atual = Loa::UnidadeOrcamentaria.find(unidade_orcamentaria_id)
		unidades_vinculadas_ids = unidade_orcamentaria_atual.unidades_orcamentaria_vinculada
			.pluck(:unidade_orcamentaria_vinculada_id)

		unidades_pelo_codigo_ids = Loa::UnidadeOrcamentaria.joins(:orgao)
			.where(codigo: unidade_orcamentaria_atual.codigo, loa_orgaos: { codigo: unidade_orcamentaria_atual.orgao.codigo }).pluck(:id)

		unidades_ids = unidades_vinculadas_ids + unidades_pelo_codigo_ids

		@itens_do_pedido_por_unidade_orcamentaria = projeto.pedido.itens_do_pedido_por_unidade_orcamentaria.joins(:unidade_orcamentaria_por_pedido).where('licitacao_unidades_orcamentarias_por_pedido.unidade_orcamentaria_id in (?)', unidades_ids)
	end

	def saldo_cotado_pela_unidade
		unidade_orcamentaria_atual = Loa::UnidadeOrcamentaria.find(unidade_orcamentaria_id)
		unidades_vinculadas_ids = unidade_orcamentaria_atual.unidades_orcamentaria_vinculada.pluck(:unidade_orcamentaria_vinculada_id)

		unidades_pelo_codigo_ids = Loa::UnidadeOrcamentaria.joins(:orgao).where(codigo: unidade_orcamentaria_atual.codigo, loa_orgaos: { codigo: unidade_orcamentaria_atual.orgao.codigo }).pluck(:id)

		unidades_ids = unidades_vinculadas_ids + unidades_pelo_codigo_ids

		unidade_orcamentaria_por_pedido = projeto.pedido.unidades_orcamentarias_por_pedido.where(unidade_orcamentaria_id: unidades_ids).first

		saldo_por_valor_previsto + saldo_por_quantidade_cotado_pela_unidade(unidade_orcamentaria_por_pedido) + saldo_por_desconto(unidade_orcamentaria_por_pedido)
	end

	def saldo_de_dotacoes_ja_utilizados
		@saldo_de_dotacoes_ja_utilizados ||= projeto.orcamentos_da_despesa_por_projetos
			.joins(orcamento_da_despesa:[elemento_de_despesa_por_subacao: [subacao: :unidade_orcamentaria]])
				.where("loa_unidades_orcamentarias.id = ?", unidade_orcamentaria_id)
					.where.not(id: self.id).sum(&:valor)
	end

	def saldo_disponivel_para_dotacao
		saldo_cotado_pela_unidade.to_d - saldo_de_dotacoes_ja_utilizados.to_d
	end

	def saldo_por_valor_previsto
		if projeto.valor_medio?
			itens_do_pedido_por_unidade_orcamentaria.por_valor_previsto_por_preco.sum(&:valor_previsto_total_preco_medio)
		elsif projeto.menor_preco? && !projeto.por_mediana?
			itens_do_pedido_por_unidade_orcamentaria.por_valor_previsto_por_preco.sum{ |ippu| ippu.valor_previsto_total_menor_preco(projeto.forma_de_agrupamento) }
		elsif projeto.por_mediana?
			itens_do_pedido_por_unidade_orcamentaria.por_valor_previsto_por_preco.sum(&:valor_previsto_total_mediana)
		else 
			0
		end
	end

	def saldo_por_desconto(unidade_orcamentaria_por_pedido)
		unidade_orcamentaria_por_pedido.itens_do_pedido_por_unidade_orcamentaria.por_valor_previsto_por_desconto.sum(:valor_previsto_desconto)
	end

	def saldo_por_quantidade_cotado_pela_unidade(unidade_orcamentaria_por_pedido)
		if unidade_orcamentaria_por_pedido.present?
			if projeto.valor_medio?
				return unidade_orcamentaria_por_pedido.try(:valor_total_preco_medio).to_d
			elsif projeto.por_mediana?
					return unidade_orcamentaria_por_pedido.try(:valor_total_mediana).to_d
			else
				if projeto.global?
					return unidade_orcamentaria_por_pedido.try(:valor_total_da_menor_cotacao_global).to_d
				else
					return unidade_orcamentaria_por_pedido.try(:valor_total_itens_por_quantidade).to_d
				end
			end
		end

		return 0
	end

	private

	def atualiza_valor
		novo_valor =
			itens_do_orcamento_da_despesa_por_projeto.inject(0) do |total, item_do_orcamento|
				total + item_do_orcamento.total_por_item.to_f
			end

		self.update_column(:valor, novo_valor) if valor.to_f != novo_valor.to_f
	end

	def nao_excede_o_valor_estimado_por_unidade_orcamentaria
		valor_cotado_atual = valor.to_d + saldo_de_dotacoes_ja_utilizados.to_d

		if valor_cotado_atual > saldo_cotado_pela_unidade.to_d
			errors.add(:valor, "O saldo disponível para a dotação é de #{saldo_disponivel_para_dotacao.real_contabil}")
		end
	end
end
