class Projecao::Receita < ApplicationRecord
	include ReceitaConcern

	attr_default :de_convenio, false

	belongs_to :projecao_de_receita
	delegate :planejamento, :to => :projecao_de_receita, :allow_nil => false

	has_many :calculo_por_exercicios, dependent: :destroy
	has_many :receitas_dos_calculos_de_projecao, dependent: :destroy
	has_many :calculo_por_exercicios_projecao, through: :receitas_dos_calculos_de_projecao, source: :calculo_por_exercicio
	has_many :convenios, -> (receita) {
		unscope(:where).where("receita_corrente_id = :id OR receita_de_capital_id = :id ", id: receita.id)
	}, class_name: "Ppa::Convenio"

	accepts_nested_attributes_for :calculo_por_exercicios, reject_if: proc { |attributes| attributes['total'].blank? }
	accepts_nested_attributes_for :calculo_por_exercicios_projecao

	validates_presence_of :tipo_de_orcamento, :projecao_de_receita_id

	validates :projecao_de_receita_id, immutable: true

	validate :pai_nao_deve_ter_calculos_por_exercicio
	

	validates_uniqueness_of :codigo, scope: [:projecao_de_receita_id], case_sensitive: false
	validates_uniqueness_of :categoria_economica, scope: [:origem, :especie, :rubrica, :alinea, :subalinea, :detalhamento_optativo, :nivel_opcional_1, :nivel_opcional_2, :nivel_opcional_3, :projecao_de_receita_id], case_sensitive: false
	validates_uniqueness_of :origem, scope: [:categoria_economica, :especie, :rubrica, :alinea, :subalinea, :detalhamento_optativo, :nivel_opcional_1, :nivel_opcional_2, :nivel_opcional_3, :projecao_de_receita_id], case_sensitive: false
	validates_uniqueness_of :especie, scope: [:categoria_economica, :origem, :rubrica, :alinea, :subalinea, :detalhamento_optativo, :nivel_opcional_1, :nivel_opcional_2, :nivel_opcional_3, :projecao_de_receita_id], case_sensitive: false
	validates_uniqueness_of :rubrica, scope: [:categoria_economica, :origem, :especie, :alinea, :subalinea, :detalhamento_optativo, :nivel_opcional_1, :nivel_opcional_2, :nivel_opcional_3, :projecao_de_receita_id], case_sensitive: false
	validates_uniqueness_of :alinea, scope: [:categoria_economica, :origem, :especie, :rubrica, :subalinea, :detalhamento_optativo, :nivel_opcional_1, :nivel_opcional_2, :nivel_opcional_3, :projecao_de_receita_id], case_sensitive: false
	validates_uniqueness_of :subalinea, scope: [:categoria_economica, :origem, :especie, :rubrica, :alinea, :detalhamento_optativo, :nivel_opcional_1, :nivel_opcional_2, :nivel_opcional_3, :projecao_de_receita_id], case_sensitive: false
	validates_uniqueness_of :detalhamento_optativo, scope: [:categoria_economica, :origem, :especie, :rubrica, :alinea, :subalinea, :nivel_opcional_1, :nivel_opcional_2, :nivel_opcional_3, :projecao_de_receita_id], case_sensitive: false
	validates_uniqueness_of :nivel_opcional_1,      scope: [:categoria_economica, :origem, :especie, :rubrica, :alinea, :subalinea, :detalhamento_optativo, :nivel_opcional_2, :nivel_opcional_3, :projecao_de_receita_id], case_sensitive: false
	validates_uniqueness_of :nivel_opcional_2,      scope: [:categoria_economica, :origem, :especie, :rubrica, :alinea, :subalinea, :detalhamento_optativo, :nivel_opcional_1, :nivel_opcional_3, :projecao_de_receita_id], case_sensitive: false
	validates_uniqueness_of :nivel_opcional_3,      scope: [:categoria_economica, :origem, :especie, :rubrica, :alinea, :subalinea, :detalhamento_optativo, :nivel_opcional_1, :nivel_opcional_2, :projecao_de_receita_id], case_sensitive: false

	scope :categorias_economicas,                 -> { where(origem: "0", especie: "0", rubrica: "0", alinea: "00", subalinea: "00", detalhamento_optativo: "00") }
	scope :de_convenio,                           -> { where(de_convenio: true) }

	enum tipo_de_orcamento: {
		fiscal: 0,
		seguridade_social: 1
	}

	def modulo
		projecao_de_receita
	end

	def tem_calculo_preenchido?
		self.calculo_por_exercicios.where(exercicio: self.projecao_de_receita.exercicio_corrente).any? {|calculo| calculo.total > 0.0}
	end

	def pai_nao_deve_ter_calculos_por_exercicio
		if self.projecao_de_receita
			pai = nivel_pai
			if pai.present? && pai.tem_calculo_preenchido?
				errors.add(:base, "não é possivel criar uma receita quando seu pai já possui cálculos por exercício")
			end
		end
	end

	def valida_analitica?
		if self.codigo.present?
			Projecao::Receita.where(classificacao_com_niveis_utilizados.merge(projecao_de_receita_id: self.projecao_de_receita_id)).where.not(codigo: self.codigo).empty?
		end
	end

	def total_agregado exercicio, tipo=nil
		if self.projecao_de_receita.exercicios_base_e_corrente.include?(exercicio)
			return total_agregado_base(exercicio,tipo)
		elsif self.projecao_de_receita.exercicios_destino.include?(exercicio)
			return total_agregado_projecao(exercicio)
		end
	end

	def total_agregado_base exercicio, tipo
		Projecao::CalculoPorExercicio.joins(:receita).where(
		projecao_receitas: self.classificacao_com_niveis_utilizados.merge({projecao_de_receita_id: self.projecao_de_receita_id}),
		projecao_calculo_por_exercicios: { exercicio: exercicio, tipo: Projecao::CalculoPorExercicio.tipos[tipo]}).sum(:total).to_f
	end

	def total_agregado_projecao exercicio
		total_projetado = Projecao::CalculoPorExercicio.joins(receita_do_calculo_de_projecao: :receita).where(
			projecao_receitas: self.classificacao_com_niveis_utilizados.merge({ projecao_de_receita_id: self.projecao_de_receita_id }),
			projecao_calculo_por_exercicios: { exercicio: exercicio }
		).sum(:total).to_f
		total_inserido = Projecao::CalculoPorExercicio.joins(:receita).where(
			projecao_receitas: self.classificacao_com_niveis_utilizados.merge({ projecao_de_receita_id: self.projecao_de_receita_id }),
			projecao_calculo_por_exercicios: { exercicio: exercicio }
		).sum(:total).to_f

		total_projetado + total_inserido
	end

	def total_agregado_projecao_consolidado
		(self.projecao_de_receita.exercicios_destino).inject(0) { |total, ano| total + total_agregado_projecao(ano) }
	end

	def total_agregado_projecao_dos_3_ultimos_anos
		(projecao_de_receita.exercicio_projecao_inicial+1..self.projecao_de_receita.exercicio_projecao_final).inject(0) { |total, ano| total + total_agregado_projecao(ano) }
	end

	def criar_calculos_por_exercicios parametro=nil
		exercicio_corrente = self.projecao_de_receita.exercicio_corrente

		self.projecao_de_receita.exercicios_base.each { |exercicio|
			self.calculo_por_exercicios.create!( exercicio: exercicio, tipo: 'realizado', receita_id: self.id, total: 0.to_f, importado: false )
		}

		self.calculo_por_exercicios.create!( exercicio: exercicio_corrente, tipo: 'orcado', receita_id: self.id, total: 0.to_f, importado: false )
		self.calculo_por_exercicios.create!( exercicio: exercicio_corrente, tipo: 'revisado', receita_id: self.id, total: 0.to_f, importado: false )

		if parametro == :orcamento
			self.calculo_por_exercicios.create!( exercicio: exercicio_corrente+1, tipo: 'previsto', total: 0.to_f, importado: false )
		end
	end

	def self.importar_receitas_dos_orcamentos_anteriores projecao_de_receita
		Projecao::Receita.transaction do
			begin
				importar_receitas( projecao_de_receita )
				criar_calculos_base_e_corrente( projecao_de_receita )
				return true
			rescue ActiveRecord::RecordInvalid
				raise ActiveRecord::Rollback
				return false
			end
		end
	end

	def atualizar_valor_de_convenio
		tipo_de_receita = self.categoria_economica.to_i

		self.planejamento.periodo.each { |ano|
			valor_convenios = self.convenios.inject(0) {|total, convenio|
				total + convenio.valores_do_convenio.select { |valor_do_convenio| valor_do_convenio.tipo_de_despesa.codigo == tipo_de_receita && valor_do_convenio.exercicio == ano }.first.valor
			}
			calculo = self.calculo_por_exercicios.find_or_initialize_by(exercicio: ano, tipo: 3)
			calculo.total = valor_convenios

			return false unless calculo.save
		}
	end

	def filhos_que_podem_ser_orcados
		hash_receita_com_niveis_utilizados = classificacao_com_niveis_utilizados
		projecao_de_receita.receitas.where(hash_receita_com_niveis_utilizados).where.not(codigo: codigo).podem_ser_orcadas
	end

	def mais_proximo_que_pode_ser_orcado
		filhos_orcados = filhos_que_podem_ser_orcados
		receita = self

		contador_limite = 0

		while filhos_orcados.blank? || contador_limite < 5
			pai = receita.nivel_pai
			if pai.present?
				filhos_orcados = pai.filhos_que_podem_ser_orcados
				receita = pai
			end
			contador_limite += 1
		end

		filhos_orcados.first
	end

	def atualizar_calculos_por_exercicios_para_nova_receita_em_exercicios_base_e_corrente
		projecao_de_receita.exercicios_base_e_corrente.to_a.each do |exercicio|
			if exercicio == projecao_de_receita.exercicio_corrente
				tipos = ['orcado', 'revisado']
				metodo_de_acesso = 'valor_previsto'
			else
				tipos = ['realizado']
				metodo_de_acesso = 'valor_total_realizado_da_receita'
			end

			# pega as receitas antigas que tem como referencia a receita corrente
			receitas_antigas = self.receitas_antigas_de_um_orcamento(exercicio)

			# uma receita nova pode receber valores de varias receitas antigas
			receitas_antigas.each do |receita_antiga|
				if receita_antiga.analitica?
					tipos.each do |tipo|
						calculo = self.calculo_por_exercicios.find_by(exercicio: exercicio, tipo: Projecao::CalculoPorExercicio.tipos[tipo])
						if calculo.present?
							calculo.assign_attributes(total: calculo.total + receita_antiga.send(metodo_de_acesso).to_f, importado: true)
							calculo.save(validate: false)
						end
					end
				elsif receita_antiga.send(metodo_de_acesso).to_f > 0
					# cria receita analitica e adiciona valor de receita nao analitica
					receita_nao_analitica_com_valor = projecao_de_receita.receitas.build(classificacao_com_niveis_utilizados.merge({detalhamento_optativo: "99", descricao: "Valores encontrados em receita sintética"}))
					receita_nao_analitica_com_valor.save(validate: false)

					tipos.each do |tipo|
						calculo = Projecao::CalculoPorExercicio.find_by(exercicio: exercicio, tipo: Projecao::CalculoPorExercicio.tipos[tipo], receita_id: receita_nao_analitica_com_valor.id)
						if calculo.blank?
							calculo = Projecao::CalculoPorExercicio.new( exercicio: exercicio, tipo: tipo, receita_id: receita_nao_analitica_com_valor.id, total: receita_antiga.send(metodo_de_acesso).to_f, importado: true )
							calculo.save
						else
							calculo.assign_attributes(total: calculo.total + receita_antiga.send(metodo_de_acesso).to_f, importado: true)
							calculo.save(validate: false)
						end
					end
				end
			end
		end
	end

	def receitas_antigas_de_um_orcamento exercicio
		orcamento = Orcamento.find_by(exercicio: exercicio)

		if orcamento.present?
			if orcamento.utiliza_novo_tipo_para_receita?
				receita = orcamento.naturezas_da_receita.where(codigo: codigo, novo_tipo: true) if orcamento.present?
			else
				receita = orcamento.naturezas_da_receita.where(codigo: codigo_referencia, novo_tipo: false) if orcamento.present?
			end
		end

		return receita || []
	end

	private
	def self.criar_receita_da_projecao projecao_de_receita, receita_do_orcamento
		Projecao::Receita.create!(
			projecao_de_receita_id: projecao_de_receita.id,
			codigo: receita_do_orcamento.codigo,
			descricao: receita_do_orcamento.descricao,
			tipo_de_orcamento: receita_do_orcamento.tipo_de_orcamento.codigo.eql?('F') ? 'fiscal' : 'seguridade_social',
			tipo_de_receita: receita_do_orcamento.tipo_de_receita.eql?(1) ? 'primaria' : 'financeira',
			padrao: true,
			novo_tipo: projecao_de_receita.planejamento.utiliza_novo_tipo_para_receita?,
			de_convenio: receita_do_orcamento.de_convenio
		)
	end

	def self.importar_receitas projecao_de_receita
		tipo_de_receita_do_novo_orcamento = Orcamento.find_by(exercicio: projecao_de_receita.exercicio_projecao_inicial).try(:novo_tipo)

		if tipo_de_receita_do_novo_orcamento.present?
			orcamentos = Orcamento.where(exercicio: projecao_de_receita.exercicio_projecao_inicial)
		else
			orcamentos = Orcamento.where(exercicio: projecao_de_receita.exercicios_base_e_corrente.to_a)
		end
		unless orcamentos.empty?
			receitas_orcamentos = Base::NaturezaDaReceita.where(modulo_id: orcamentos.pluck(:id)).select('distinct on (codigo) *')
			receitas_orcamentos.each { |receita| criar_receita_da_projecao(projecao_de_receita, receita) }
		else
			projecao_de_receita.cria_receitas
		end
	end

	def self.criar_calculos_base_e_corrente projecao_de_receita
		nao_analiticas = projecao_de_receita.receitas - projecao_de_receita.receitas.analiticas
		projecao_de_receita.exercicios_base_e_corrente.to_a.each do |exercicio|
			unless exercicio.present?
				if exercicio == projecao_de_receita.exercicio_corrente
					tipos = ['orcado', 'revisado']
					metodo_de_acesso = 'valor_previsto'
				else
					tipos = ['realizado']
					metodo_de_acesso = 'valor_total_realizado_da_receita'
				end

				orcamento = Orcamento.find_by(exercicio: exercicio)

				nao_analiticas.each{ |receita|
					receita_do_orcamento =  orcamento.present? ? orcamento.naturezas_da_receita.find_by(codigo: receita.codigo) : nil
					if orcamento && receita_do_orcamento
						if receita_do_orcamento.send(metodo_de_acesso).to_f > 0
							anomalia_do_tcm = projecao_de_receita.receitas.build(receita.classificacao_com_niveis_utilizados.merge({detalhamento_optativo: "99", descricao: "Valores encontrados em receita sintética"}))
							anomalia_do_tcm.save(validate: false)
							tipos.each do |tipo|
								Projecao::CalculoPorExercicio.create!( exercicio: exercicio, tipo: tipo, receita_id: anomalia_do_tcm.id, total: receita_do_orcamento.send(metodo_de_acesso).to_f, importado: true )
							end
						end
					end
				}

				projecao_de_receita.receitas.analiticas.each do |receita|
					receita_do_orcamento = orcamento.present? ? orcamento.naturezas_da_receita.find_by(codigo: receita.codigo) : nil
					if orcamento && receita_do_orcamento
						tipos.each do |tipo|
							Projecao::CalculoPorExercicio.create!( exercicio: orcamento.exercicio, tipo: tipo, receita_id: receita.id, total: receita_do_orcamento.send(metodo_de_acesso).to_f, importado: true )
						end
					else
						tipos.each do |tipo|
							Projecao::CalculoPorExercicio.create!( exercicio: exercicio, tipo: tipo, receita_id: receita.id, total: 0.0, importado: false )
						end
					end
				end
			end
		end
	end
end
