require 'active_support/concern'

module ReceitaConcern extend ActiveSupport::Concern
	included do
		attr_default :analitica, true

		validates :novo_tipo, immutable: true
		validates :padrao, immutable: true

		validates_presence_of :codigo, :descricao, :categoria_economica, :origem, :especie, :rubrica, :alinea, :subalinea, :detalhamento_optativo, :tipo_de_receita
		validates_inclusion_of :analitica, in: [true, false]
		validates_length_of :origem, is: 1
		validates_length_of :especie, is: 1
		validates_length_of :rubrica, is: 1
		validates_length_of :descricao, maximum: 200

		before_validation :atribui_classificacao_da_receita

		validate :codigo_deve_estar_com_os_atributos_de_niveis_de_classificacao
		validate :niveis_devem_ser_todos_maiores_ou_igual_a_zero

		after_create :muda_atributo_analitica_do_pai

		before_destroy :impede_delecao_para_receita_com_filhos, if: :novo_tipo?
		after_destroy :muda_atributo_analitica_do_pai

		scope :corrente,                              -> (novo_tipo=nil) { novo_tipo ? find_by(codigo: '0010000000000000') : find_by(codigo: '1000000000') }
		scope :capital,                               -> (novo_tipo=nil) { novo_tipo ? find_by(codigo: '0020000000000000') : find_by(codigo: '2000000000') }
		scope :correntes,                             -> (novo_tipo=nil) { novo_tipo ? where(categoria_economica: '001') : where(categoria_economica: '1') }
		scope :de_capital,                            -> (novo_tipo=nil) { novo_tipo ? where(categoria_economica: '002') : where(categoria_economica: '2') }
		scope :receita_corrente_intra_orcamentaria,   -> (novo_tipo=nil) { novo_tipo ? find_by(codigo: '0070000000000000') : find_by(codigo: '7000000000') }
		scope :receita_de_capital_intra_orcamentaria, -> (novo_tipo=nil) { novo_tipo ? find_by(codigo: '0080000000000000') : find_by(codigo: '8000000000')  }
		scope :analiticas, -> { where(analitica: true) }
		scope :podem_ser_orcadas, -> { select(&:pode_ser_orcada?) }

		scope :origens, -> (novo_tipo=nil) {
			if novo_tipo
				where(especie: "0", rubrica: "0", alinea: "00", subalinea: "0", detalhamento_optativo: "0", nivel_opcional_1: '00', nivel_opcional_2: '00', nivel_opcional_3: '00').where.not(origem: "0").order(:origem)
			else
				where(especie: "0", rubrica: "0", alinea: "00", subalinea: "00", detalhamento_optativo: "00").where.not(origem: "0").order(:origem)
			end
		}

		scope :especies, -> (novo_tipo=nil) {
			if novo_tipo
				where(rubrica: "0", alinea: "00", subalinea: "0", detalhamento_optativo: "0", nivel_opcional_1: '00', nivel_opcional_2: '00', nivel_opcional_3: '00').where.not(origem: "0").order(:origem)
			else
				where(rubrica: "0", alinea: "00", subalinea: "00", detalhamento_optativo: "00").where.not(origem: "0").order(:origem)
			end
		}

		scope :deducoes_da_receita, -> (novo_tipo=nil) {

			if novo_tipo
				where(categoria_economica: "009", especie: "0", rubrica: "0", alinea: "00", subalinea: "0", detalhamento_optativo: "0", nivel_opcional_1: '00', nivel_opcional_2: '00', nivel_opcional_3: '00').order(:origem)
			else
				where(categoria_economica: "9", especie: "0", rubrica: "0", alinea: "00", subalinea: "00", detalhamento_optativo: "00").order(:origem)
			end
		}

		scope :categorias_economicas, -> (novo_tipo=nil) {
			if novo_tipo
				where(origem: "0", especie: "0", rubrica: "0", alinea: "00", subalinea: "0", detalhamento_optativo: "0").order(:categoria_economica)
			else
				where(origem: "0", especie: "0", rubrica: "0", alinea: "00", subalinea: "00", detalhamento_optativo: "00").order(:categoria_economica)
			end
		}

		enum tipo_de_receita: {
			primaria: 0,
			financeira: 1
		}

		def self.completar_codigo_com_zeros(codigo, utiliza_novo_tipo_de_codigo)
			if utiliza_novo_tipo_de_codigo
				codigo.ljust(16, '0')
			else
				codigo.ljust(10, '0')
			end
		end

		def self.classificacao_por_codigo(codigo, novo_tipo)
			if novo_tipo
				{
					categoria_economica: codigo[0..2],
					origem: codigo[3],
					especie: codigo[4],
					rubrica: codigo[5],
					alinea: codigo[6..7],
					subalinea: codigo[8],
					detalhamento_optativo: codigo[9],
					nivel_opcional_1: codigo[10..11],
					nivel_opcional_2: codigo[12..13],
					nivel_opcional_3: codigo[14..15]
				}
			else
				{
					categoria_economica: codigo[0],
					origem: codigo[1],
					especie: codigo[2],
					rubrica: codigo[3],
					alinea: codigo[4..5],
					subalinea: codigo[6..7],
					detalhamento_optativo: codigo[8..9]
				}
			end
		end

		def self.classificacao_com_niveis_utilizados_por_codigo(codigo, utiliza_novo_tipo_de_codigo)
			self.classificacao_por_codigo(codigo, utiliza_novo_tipo_de_codigo).to_a.reverse.to_h.drop_while { |nivel, valor| valor.to_i == 0 }.reverse.to_h
		end

		def self.codigo_formatado(codigo)
			if codigo.to_s.size > 10
				return codigo[0..2] + '.' + codigo[3] + '.' + codigo[4] + '.' + codigo[5] + '.' + codigo[6..7] +
					'.' + codigo[8] + '.' + codigo[9] + '.' + codigo[10..11] + '.' + codigo[12..13] + '.' + codigo[14..15]
			else
				return codigo[0] + '.' + codigo[1] + '.' + codigo[2] + '.' + codigo[3] + '.' + codigo[4..5] + '.' + codigo[6..7] + '.' + codigo[8..9]
			end
		end

		def self.codigo_da_nova_receita_intra_orcamentaria codigo
			if codigo[2] == '1'
				return '7'
			elsif codigo[2] == '2'
				return '8'
			end
		end
	end

	def descricao_e_sigla
		sigla.present? ? "#{descricao} (#{sigla.to_s.upcase})" : descricao
	end

	def codigo_e_descricao
		"#{codigo} - #{descricao}"
	end

	def codigo_formatado_e_descricao
		"#{classificacao.try(:formatar)} - #{descricao}"
	end
	
	def codigo_formatado
		"#{classificacao.try(:formatar)}"
	end

	def filhos
		self.class.where(classificacao_com_niveis_utilizados.merge(params_modulo)).where.not(codigo: codigo)
	end

	def pode_ser_orcada?
		return analitica?
	end

	def pode_ser_configurada?
		codigo.nil? || (receita_orcamentaria? && detalhamento_optativo != "0" && !receita_opcional?)
	end

	def receita_orcamentaria? novo_tipo = {novo_tipo: true}
		if novo_tipo[:novo_tipo]
			codigo[0..1] == "00" && (codigo[2] == '1' || codigo[2] == '2' || codigo[2] == '7')
		else
			categoria_economica == '1' || categoria_economica == '2' || categoria_economica == '7'
		end
	end

	def numero_pai
		categoria_economica + origem + especie + rubrica + alinea
	end

	def classificacao
		if codigo.present?
			if novo_tipo
				{
					categoria_economica: codigo[0..2],
					origem: codigo[3],
					especie: codigo[4],
					rubrica: codigo[5],
					alinea: codigo[6..7],
					subalinea: codigo[8],
					detalhamento_optativo: codigo[9],
					nivel_opcional_1: codigo[10..11],
					nivel_opcional_2: codigo[12..13],
					nivel_opcional_3: codigo[14..15]
				}
			else
				{
					categoria_economica: codigo[0],
					origem: codigo[1],
					especie: codigo[2],
					rubrica: codigo[3],
					alinea: codigo[4..5],
					subalinea: codigo[6..7],
					detalhamento_optativo: codigo[8..9]
				}
			end
		end
	end

	def codigo_matriz
		codigo[2..9]
	end

	def classificacao_com_niveis_utilizados
		classificacao.to_a.reverse.to_h.drop_while { |nivel, valor| valor.to_i == 0 }.reverse.to_h
	end

	def classificacao_com_niveis_nao_utilizados
		classificacao.delete_if do |nivel, valor|
			(['alinea', 'subalinea', 'detalhamento_optativo', 'nivel_opcional_1',
				'nivel_opcional_2', 'nivel_opcional_3'].include?(nivel.to_s) && valor.to_i != 0) or
				['categoria_economica', 'origem', 'especie', 'rubrica'].include?(nivel.to_s)
		end
	end

	def classificacao_com_niveis_utilizados_e_obrigatorios_em_string
		classificacao = classificacao_com_niveis_utilizados.values
		until classificacao.size >= 4 do classificacao << '0' end
		classificacao.join('.')
	end

	def codigo_deve_estar_com_os_atributos_de_niveis_de_classificacao
		if novo_tipo
			unless "#{codigo}" == "#{categoria_economica}#{origem}#{especie}#{rubrica}#{alinea}#{subalinea}#{detalhamento_optativo}#{nivel_opcional_1}#{nivel_opcional_2}#{nivel_opcional_3}"
				errors.add(:codigo, "código não está correspondendo aos niveis de classificacao da receita indicado")
			end
		else
			unless "#{codigo}" == "#{categoria_economica}#{origem}#{especie}#{rubrica}#{alinea}#{subalinea}#{detalhamento_optativo}"
				errors.add(:codigo, "código não está correspondendo aos niveis de classificacao da receita indicado")
			end
		end

	end

	def valida_analitica?
		if self.persisted?
			classificacao_da_receita_com_niveis_utilizados = classificacao_com_niveis_utilizados
			self.class.where(classificacao_da_receita_com_niveis_utilizados.merge(params_modulo)).where.not(codigo: self.codigo).empty?
		end
	end

	def codigo_da_classificacao
		classificacao_com_niveis_utilizados.values.last
	end

	def receita_opcional?
		nivel_opcional_1.to_i != 0 ||
		nivel_opcional_2.to_i != 0 ||
		nivel_opcional_3.to_i != 0
	end

	def categoria
		if novo_tipo
			codigo[2]
		else
			codigo[0]
		end
	end

	def intra_orcamentaria?
		['7','8'].include?(categoria)
	end

	def nivel_pai
		classificacao_em_array = classificacao_com_niveis_utilizados.values
		classificacao_em_array.pop

		if classificacao_em_array.present?
			codigo = self.class.completar_codigo_com_zeros(classificacao_em_array.join, novo_tipo)
			self.class.find_by(params_modulo.merge(codigo: codigo))
		else
			nil
		end
	end

	def detalhar_classificacao
		classificacao = classificacao_com_niveis_utilizados.values

		codigo_da_iteracao = ''
		classificacao.each_with_index.map do |nivel, index|
			codigo_da_iteracao += nivel
			codigo_completo = self.class.completar_codigo_com_zeros(codigo_da_iteracao, novo_tipo)
			natureza_da_iteracao = self.class.find_by(params_modulo.merge(codigo: codigo_completo))
			{
				codigo: natureza_da_iteracao.try(:classificacao).try(:formatar),
				descricao: natureza_da_iteracao.try(:descricao),
				posicao_de_destaque: index
			}
		end
	end

	def contexto
		if self.is_a? Base::NaturezaDaReceita
			self.modulo
		elsif self.is_a? Projecao::Receita
			self.projecao_de_receita.planejamento
		end
	end

	private
	def niveis_devem_ser_todos_maiores_ou_igual_a_zero
		niveis = [categoria_economica, origem, especie, rubrica, alinea, subalinea, detalhamento_optativo]
		niveis.each do |nivel|
			unless /\A\d+\z/.match(nivel) && nivel.to_i >= 0
				errors.add(:codigo, "código deverá ser um número inteiro positivo")
			end
		end
	end

	def muda_atributo_analitica_do_pai
		nivel_pai.update_column(:analitica, nivel_pai.valida_analitica?) if nivel_pai
	end

	def atribui_classificacao_da_receita
		if self.codigo.present? && self.descricao.present?
			if novo_tipo
				receita_referencia = Base::ReceitaStn.hash_com_codigos_antigos_e_novos.find{|receita| receita["codigo_novo"] == codigo[2..9]}

				self.categoria_economica = self.codigo[0..2]
				self.origem = self.codigo[3]
				self.especie = self.codigo[4]
				self.rubrica = self.codigo[5]
				self.alinea = self.codigo[6..7]
				self.subalinea = self.codigo[8]
				self.detalhamento_optativo = self.codigo[9]
				self.nivel_opcional_1 = self.codigo[10..11]
				self.nivel_opcional_2 = self.codigo[12..13]
				self.nivel_opcional_3 = self.codigo[14..15]
				self.codigo_referencia = receita_referencia["codigo_antigo"] if receita_referencia.present?
			else
				self.categoria_economica = self.codigo[0]
				self.origem = self.codigo[1]
				self.especie = self.codigo[2]
				self.rubrica = self.codigo[3]
				self.alinea = self.codigo[4..5]
				self.subalinea = self.codigo[6..7]
				self.detalhamento_optativo = self.codigo[8..9]
			end
		end
	end

	def params_modulo
		if self.is_a? Base::NaturezaDaReceita
			{modulo_id: modulo_id, modulo_type: modulo_type}
		elsif self.is_a? Projecao::Receita
			{projecao_de_receita_id: projecao_de_receita_id}
		end
	end

	def impede_delecao_para_receita_com_filhos
		if filhos.any?
			raise Exception.new('não é possível remover natureza da receita com filho(s)')
		end
	end
end
